Glance で UI を作成する

このページでは、サイズを適切に処理し、柔軟性とレスポンシブ デザインを持たせる方法について説明します。 Glance の既存の Glance コンポーネントを使用します。

BoxColumnRow を使用する

Glance には、主に次の 3 つのコンポーザブル レイアウトがあります。

  • Box: 要素を別の要素の上に重ねて配置します。これは RelativeLayout に変換されます。

  • Column: 要素を縦軸で並べます。翻訳機能 縦向きの LinearLayout にマッピング。

  • Row: 要素を横軸に並べます。翻訳機能 横向きの LinearLayout に変更します。

Glance は Scaffold オブジェクトをサポートしています。ColumnRow、 特定の Scaffold オブジェクト内の Box コンポーザブル。

列、行、ボックスのレイアウトの画像。
図 1. Column、Row、Box を使用したレイアウトの例。
で確認できます。

これらのコンポーザブルのそれぞれで、垂直方向と水平方向の配置を定義できます。 コンテンツの幅、高さ、太さ、パディングの制約を、 使用します。さらに、各子は修飾子を定義してスペースを変更できます 親内のプレースメントです

次の例は、均等に分散する Row の作成方法を示しています。 その子を水平方向にスケールします(図 1 を参照)。

Row(modifier = GlanceModifier.fillMaxWidth().padding(16.dp)) {
    val modifier = GlanceModifier.defaultWeight()
    Text("first", modifier)
    Text("second", modifier)
    Text("third", modifier)
}

Row は利用可能な最大幅を埋めます。また、それぞれの子は同じ幅を持ちます。 利用可能なスペースを均等に共有します。さまざまな重みを定義したり サイズ、パディング、配置を調整し、ニーズに合わせてレイアウトを調整できます。

スクロール可能なレイアウトを使用する

レスポンシブ コンテンツを提供するもう 1 つの方法は、スクロール可能にすることです。これは、 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 を指定していないことに注意してください。Pod の itemId は、パフォーマンスの向上とスクロールの維持に役立ちます。 Android 12 以降の appWidget の更新( (例: リストのアイテムの追加または削除)。次の例をご覧ください。 は、itemId を指定する方法を示しています。

items(items = peopleList, key = { person -> person.id }) { person ->
    Text(person.name)
}

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 つのサイズと content:

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×110 203×112 72×72 203×150
SizeMode.Single 110×110 110×110 110×110 110×110
SizeMode.Exact 105×110 203×112 72×72 203×150
SizeMode.Responsive 80×100 80×100 80×100 150×120
* 正確な値はデモ用です。

SizeMode.Exact

SizeMode.Exact は、正確なレイアウトを指定することと同等です。 利用可能な AppWidget サイズが返されるたびに GlanceAppWidget コンテンツをリクエスト (ユーザーがホーム画面で AppWidget のサイズを変更したときなど)。

たとえば、デスティネーション ウィジェットで、 利用可能な幅が特定の値を超えています。

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 クラスの fontSizefontWeight、または 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 { mutableStateOf(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) }
        )
    }
}

colors 属性は、CheckBoxSwitch、および RadioButton を使用して色をカスタマイズします。

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 には、追加コンポーネントのリリースが含まれています。 次の表をご覧ください。

名前 画像 参照リンク その他の注意事項
塗りつぶしボタン alt_text コンポーネント
アウトライン ボタン alt_text コンポーネント
アイコンボタン alt_text コンポーネント メイン / セカンダリ / アイコンのみ
タイトルバー alt_text コンポーネント
Scaffold スキャフォールドとタイトルバーは同じデモにあります。

設計の詳細については、こちらのコンポーネント設計に関する Design kit を使用してください。