このページでは、既存の Glance コンポーネントを使用して、Glance でサイズを処理し、柔軟でレスポンシブなレイアウトを提供する方法について説明します。
Box、Column、Row を使用する
Glance には、次の 3 つの主要なコンポーザブル レイアウトがあります。
Box: 要素を重ねて配置します。RelativeLayoutに変換されます。Column: 要素を縦軸に沿って配置します。縦向きのLinearLayoutに変換されます。Row: 要素を横軸に沿って配置します。横向きのLinearLayoutに変換されます。
Glance は Scaffold オブジェクトをサポートしています。Column、Row、Box のコンポーザブルは、指定された Scaffold オブジェクト内に配置します。
これらのコンポーザブルを使用すると、修飾子を使用して、コンテンツの縦方向と横方向の配置、幅、高さ、重み、パディングの制約を定義できます。また、各子要素は修飾子を定義して、親要素内のスペースと配置を変更できます。
次の例は、図 1 に示すように、子要素を水平方向に均等に配置する Row を作成する方法を示しています。
Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) { val modifier = GlanceModifier.defaultWeight() Text("first", modifier) Text("second", modifier) Text("third", modifier) }
Row は使用可能な最大幅を埋めます。各子要素の重みが同じであるため、使用可能なスペースを均等に共有します。さまざまな重み、サイズ、パディング、配置を定義して、レイアウトをニーズに合わせて調整できます。
スクロール可能なレイアウトを使用する
レスポンシブ コンテンツを提供する方法として、スクロール可能にする方法もあります。これは、LazyColumn コンポーザブルで実現できます。このコンポーザブルを使用すると、アプリ ウィジェットのスクロール可能なコンテナ内に表示するアイテムのセットを定義できます。
次のスニペットは、LazyColumn 内でアイテムを定義するさまざまな方法を示しています。
アイテム数を指定できます。
// Remember to import Glance Composables // import androidx.glance.appwidget.layout.LazyColumn LazyColumn { items(10) { index: Int -> Text( text = "Item $index", modifier = GlanceModifier.fillMaxWidth() ) } }
個々のアイテムを指定します。
LazyColumn { item { Text("First Item") } item { Text("Second Item") } }
アイテムのリストまたは配列を指定します。
LazyColumn { items(peopleNameList) { name -> Text(name) } }
上記の例を組み合わせて使用することもできます。
LazyColumn { item { Text("Names:") } items(peopleNameList) { name -> Text(name) } // or in case you need the index: itemsIndexed(peopleNameList) { index, person -> Text("$person at index $index") } }
前のスニペットでは itemId が指定されていません。itemId を指定すると、Android 12 以降のリストと appWidget の更新を通じて、パフォーマンスの向上とスクロール位置の維持に役立ちます(たとえば、リストからアイテムを追加または削除する場合)。次の例は、itemId を指定する方法を示しています。
items(items = peopleList, itemId = { person -> person.id.hashCode().toLong() }) { person -> Text(person.name) }
スナップ スクロール
スナップ スクロールは、スクロール可能なコンテンツをウィジェット コンテナの上部に自動整列させるアニメーションです。
スナップ スクロールを実装するには、次の条件を満たしていることを確認してください。
- Glance の 依存関係 を 1.3.0-alpha02 以降に更新します。
compileSdkを 37 以降に設定します。スナップ スクロールは Android 17 以降を搭載したデバイスでサポートされています。VerticalScrollModeでLazyColumnを構成します。デバイスがスナップ スクロールをサポートしている場合は、SnapScrollMatchHeightを使用します。それ以外の場合は、Normalを使用します。
画像でスナップ スクロールを使用する場合は、フルブリード 画像の正規レイアウトをご覧ください。
@Composable fun SnapScrollLayout() { val height = LocalSize.current.height val items = listOf( ColorItem(Color.Red, "Red"), ColorItem(Color.Yellow, "Yellow"), ColorItem(Color.Blue, "Blue") ) val scrollMode = if (Build.VERSION.SDK_INT >= 37) { VerticalScrollMode.SnapScrollMatchHeight(height) } else { VerticalScrollMode.Normal } LazyColumn( verticalScrollMode = scrollMode ) { items(items) { item -> ColorCard(item, height) } } } @Composable private fun ColorCard(item: ColorItem, height: Dp) { Box( modifier = GlanceModifier .background(item.color) .fillMaxWidth() .height(height), contentAlignment = Alignment.Center ) { Text( text = item.name, modifier = GlanceModifier.background(Color.White) ) } }
SizeMode を定義する
AppWidget のサイズは、デバイス、ユーザーの選択、ランチャーによって異なる場合があります。
そのため、柔軟なウィジェット レイアウトを提供するで説明されているように、柔軟なレイアウトを提供することが重要です。Glance では、SizeMode の定義と LocalSize の値を使用して、これを簡素化しています。以降のセクションでは、3 つのモードについて説明します。
SizeMode.Single
SizeMode.Single はデフォルト モードです。これは、1 種類のコンテンツのみが提供されることを示します。つまり、AppWidget の使用可能なサイズが変更されても、コンテンツのサイズは変更されません。
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Single override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the minimum size or resizable // size defined in the App Widget metadata val size = LocalSize.current // ... } }
このモードを使用する場合は、次のことを確認してください。
- 最小サイズと最大サイズのメタデータ値が、コンテンツのサイズに基づいて適切に定義されている。
- コンテンツが想定されるサイズ範囲内で十分な柔軟性がある。
一般に、このモードは次のいずれかに該当する場合に使用します。
a) AppWidget のサイズが固定されている場合、または b) サイズ変更時にコンテンツが変更されない場合。
SizeMode.Responsive
このモードは、レスポンシブ レイアウトを提供するのと同じです。これにより、
GlanceAppWidget は特定の
サイズで区切られたレスポンシブ レイアウトのセットを定義できます。定義されたサイズごとに、AppWidget の作成時または更新時にコンテンツが作成され、特定のサイズにマッピングされます。システムは、使用可能なサイズに基づいて最適なものを選択します。
たとえば、宛先の AppWidget で、3 つのサイズとそのコンテンツを定義できます。
class MyAppWidget : GlanceAppWidget() { companion object { private val SMALL_SQUARE = DpSize(100.dp, 100.dp) private val HORIZONTAL_RECTANGLE = DpSize(250.dp, 100.dp) private val BIG_SQUARE = DpSize(250.dp, 250.dp) } override val sizeMode = SizeMode.Responsive( setOf( SMALL_SQUARE, HORIZONTAL_RECTANGLE, BIG_SQUARE ) ) override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be one of the sizes defined above. val size = LocalSize.current Column { if (size.height >= BIG_SQUARE.height) { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) } Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width >= HORIZONTAL_RECTANGLE.width) { Button("School") } } if (size.height >= BIG_SQUARE.height) { Text(text = "provided by X") } } } }
前の例では、provideContent メソッドが 3 回呼び出され、定義されたサイズにマッピングされています。
- 最初の呼び出しでは、サイズは
100x100に評価されます。コンテンツには、追加のボタンと上部と下部のテキストは含まれません。 - 2 回目の呼び出しでは、サイズは
250x100に評価されます。コンテンツには追加のボタンが含まれますが、上部と下部のテキストは含まれません。 - 3 回目の呼び出しでは、サイズは
250x250に評価されます。コンテンツには、追加のボタンと両方のテキストが含まれます。
SizeMode.Responsive は他の 2 つのモードの組み合わせで、事前定義された境界内でレスポンシブ コンテンツを定義できます。一般に、このモードはパフォーマンスが向上し、AppWidget のサイズ変更時の移行がスムーズになります。
次の表に、SizeMode と AppWidget の使用可能なサイズに応じたサイズの値を示します。
| 使用可能なサイズ | 105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
|---|---|---|---|---|
SizeMode.Single |
110 x 110 | 110 x 110 | 110 x 110 | 110 x 110 |
SizeMode.Exact |
105 x 110 | 203 x 112 | 72 x 72 | 203 x 150 |
SizeMode.Responsive |
80 x 100 | 80 x 100 | 80 x 100 | 150 x 120 |
| * 正確な値はデモ専用です。 |
SizeMode.Exact
SizeMode.Exact は 正確なレイアウトを提供するのと同じです。これにより、使用可能な AppWidget のサイズが変更されるたびに(ユーザーがホーム画面で AppWidget のサイズを変更した場合など)、GlanceAppWidget コンテンツがリクエストされます。
たとえば、宛先ウィジェットで、使用可能な幅が特定の値より大きい場合は、追加のボタンを追加できます。
class MyAppWidget : GlanceAppWidget() { override val sizeMode = SizeMode.Exact override suspend fun provideGlance(context: Context, id: GlanceId) { // ... provideContent { MyContent() } } @Composable private fun MyContent() { // Size will be the size of the AppWidget val size = LocalSize.current Column { Text(text = "Where to?", modifier = GlanceModifier.padding(12.dp)) Row(horizontalAlignment = Alignment.CenterHorizontally) { Button() Button() if (size.width > 250.dp) { Button("School") } } } } }
このモードは他のモードよりも柔軟性がありますが、いくつかの注意点があります。
- サイズが変更されるたびに、
AppWidgetを完全に再作成する必要があります。コンテンツが複雑な場合、パフォーマンスの問題や UI のジャンプが発生する可能性があります。 - 使用可能なサイズは、ランチャーの実装によって異なる場合があります。 たとえば、ランチャーがサイズのリストを提供しない場合は、可能な最小サイズが使用されます。
- Android 12 より前のデバイスでは、サイズ計算ロジックがすべての状況で機能しない可能性があります。
一般に、SizeMode.Responsive を使用できない場合(つまり、レスポンシブ レイアウトのセットが少ない場合)は、このモードを使用する必要があります。
リソースにアクセスする
次の例に示すように、LocalContext.current を使用して Android リソースにアクセスします。
LocalContext.current.getString(R.string.glance_title)
リソース ID を直接指定して、最終的な
RemoteViews オブジェクトのサイズを縮小し、動的な色などの動的リソースを有効にすることをおすすめします。
コンポーザブルとメソッドは、ImageProvider などの「プロバイダ」を使用するか、GlanceModifier.background(R.color.blue) などのオーバーロード メソッドを使用してリソースを受け入れます。例:
Column( modifier = GlanceModifier.background(R.color.default_widget_background) ) { /**...*/ } Image( provider = ImageProvider(R.drawable.ic_logo), contentDescription = "My image", )
テキストを処理する
Glance 1.1.0 には、テキスト スタイルを設定する API が含まれています。TextStyle クラスの fontSize、fontWeight、fontFamily 属性を使用してテキスト スタイルを設定します。
fontFamily は、次の例に示すように、すべてのシステム フォントをサポートしていますが、アプリのカスタム フォントはサポートされていません。
Text(
style = TextStyle(
fontWeight = FontWeight.Bold,
fontSize = 18.sp,
fontFamily = FontFamily.Monospace
),
text = "Example Text"
)
複合ボタンを追加する
複合ボタンは Android 12 で導入されました。Glance は、次のタイプの複合ボタンの下位互換性をサポートしています。
これらの複合ボタンはそれぞれ、[オン] の状態を表すクリック可能なビューを表示します。
var isApplesChecked by remember { mutableStateOf(false) } var isEnabledSwitched by remember { mutableStateOf(false) } var isRadioChecked by remember { mutableIntStateOf(0) } CheckBox( checked = isApplesChecked, onCheckedChange = { isApplesChecked = !isApplesChecked }, text = "Apples" ) Switch( checked = isEnabledSwitched, onCheckedChange = { isEnabledSwitched = !isEnabledSwitched }, text = "Enabled" ) RadioButton( checked = isRadioChecked == 1, onClick = { isRadioChecked = 1 }, text = "Checked" )
状態が変更されると、指定されたラムダがトリガーされます。次の例に示すように、オンの状態を保存できます。
class MyAppWidget : GlanceAppWidget() { override suspend fun provideGlance(context: Context, id: GlanceId) { val myRepository = MyRepository.getInstance() provideContent { val scope = rememberCoroutineScope() val saveApple: (Boolean) -> Unit = { scope.launch { myRepository.saveApple(it) } } MyContent(saveApple) } } @Composable private fun MyContent(saveApple: (Boolean) -> Unit) { var isAppleChecked by remember { mutableStateOf(false) } Button( text = "Save", onClick = { saveApple(isAppleChecked) } ) } }
CheckBox、Switch、RadioButton に colors 属性を指定して、色をカスタマイズすることもできます。
CheckBox( // ... colors = CheckboxDefaults.colors( checkedColor = ColorProvider(day = colorAccentDay, night = colorAccentNight), uncheckedColor = ColorProvider(day = Color.DarkGray, night = Color.LightGray) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked } ) Switch( // ... colors = SwitchDefaults.colors( checkedThumbColor = ColorProvider(day = Color.Red, night = Color.Cyan), uncheckedThumbColor = ColorProvider(day = Color.Green, night = Color.Magenta), checkedTrackColor = ColorProvider(day = Color.Blue, night = Color.Yellow), uncheckedTrackColor = ColorProvider(day = Color.Magenta, night = Color.Green) ), checked = isChecked, onCheckedChange = { isChecked = !isChecked }, text = "Enabled" ) RadioButton( // ... colors = RadioButtonDefaults.colors( checkedColor = ColorProvider(day = Color.Cyan, night = Color.Yellow), uncheckedColor = ColorProvider(day = Color.Red, night = Color.Blue) ), )
追加コンポーネント
Glance 1.1.0 では、次の表に示すように、追加のコンポーネントがリリースされています。
| 名前 | 画像 | 参照リンク | その他の注意事項 |
|---|---|---|---|
| 塗りつぶしボタン |
|
コンポーネント | |
| アウトライン ボタン |
|
コンポーネント | |
| アイコンボタン |
|
コンポーネント | メイン / サブ / アイコンのみ |
| タイトルバー |
|
コンポーネント | |
| Scaffold | Scaffold とタイトルバーは同じデモにあります。 |
設計の詳細については、Figma のこの デザインキットのコンポーネント設計をご覧ください。
正規レイアウトの詳細については、正規ウィジェット レイアウトをご覧ください。