Wear OS 向け Compose でのリスト


リストを使用すると、Wear OS デバイス上でユーザーが選択肢の中からアイテムを選択できるようになります。

多くの Wear OS デバイスは円形の画面であるため、画面の上部や下部付近に表示されるリストアイテムが見づらくなっています。このため、Wear OS 向け Compose には、スケーリングおよびモーフィング アニメーションをサポートする TransformingLazyColumn という LazyColumn クラスのバージョンが含まれています。アイテムが端に移動すると、アイテムは小さくなり、フェードアウトします。

推奨される拡大縮小とスクロールの効果を適用するには:

  1. Compose で、アイテムが画面をスクロールする際の高さの変化を計算できるようにするには、Modifier.transformedHeight を使用します。
  2. transformation = SurfaceTransformation(transformationSpec) を使用して、アイテムのコンテンツの縮小などの視覚効果を適用します。
  3. transformation をパラメータとして受け取らないコンポーネント(Text など)には、カスタムの TransformationSpec を使用します。

次のアニメーションは、リスト要素が画面の上部と下部に近づくにつれて、どのように拡大縮小され、形状が変化するかを示しています。

次のコード スニペットは、TransformingLazyColumn レイアウトを使用して、さまざまな Wear OS 画面サイズで美しく表示されるコンテンツを作成するリストを作成する方法を示しています。

このスニペットでは、リストの上下に適切なパディングを適用するために、リスト項目に設定する必要がある minimumVerticalContentPadding 修飾子の使用方法も示しています。

スクロール インジケーターを表示するには、ScreenScaffoldTransformingLazyColumn の間で columnState を共有します。

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(
    scrollState = columnState
) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding
    ) {
        item {
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text(text = "Header")
            }
        }
        // ... other items
        item {
            Button(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec),
                onClick = { /* ... */ },
                icon = {
                    Icon(
                        imageVector = Icons.Default.Build,
                        contentDescription = "build",
                    )
                },
            ) {
                Text(
                    text = "Build",
                    maxLines = 1,
                    overflow = TextOverflow.Ellipsis,
                )
            }
        }
    }
}

スナップ&フリング エフェクトを追加する

スナップとは、ユーザーがスクロールまたはフリングの操作を完了したときに、リストが特定のポイント(通常は画面の中央)に正確に配置された状態で落ち着くようにすることです。丸い画面では、アイテムが中央から離れるにつれて拡大縮小や変形が行われるため、スナップは、最も関連性の高いアイテムが最適な表示領域で完全に表示され、読み取り可能であることを保証するうえで特に有用です。

スナップ&フリングの動作を追加するには、flingBehavior パラメータを TransformingLazyColumnDefaults.snapFlingBehavior(columnState) に設定します。物理クラウンまたはベゼルを使用する際に一貫したエクスペリエンスを実現するには、rotaryScrollableBehavior を一致するように設定し、RotaryScrollableDefaults.snapBehavior(columnState) を使用します。

val columnState = rememberTransformingLazyColumnState()
ScreenScaffold(scrollState = columnState) {
    TransformingLazyColumn(
        state = columnState,
        flingBehavior = TransformingLazyColumnDefaults.snapFlingBehavior(columnState),
        rotaryScrollableBehavior = RotaryScrollableDefaults.snapBehavior(columnState)
    ) {
        // ...
        // ...
    }
}

反転レイアウト

デフォルトでは、スクロール可能なリストは上端に固定されます。ユーザーが標準リストの最下部までスクロールし、新しいアイテムが末尾に追加された場合、リストは現在のアイテムに対するユーザーのビューを維持します。たとえば、ユーザーが画面の下部にあるアイテム 10 を表示しているときにアイテム 11 が追加されると、ビューはアイテム 10 にフォーカスされたままになり、アイテム 11 は現在のビューの下の画面外に表示されます。

メッセージ アプリケーションやライブログなどのユースケースでは、通常、この動作は望ましくありません。新しいアイテムが届いた場合、ユーザーは通常、リストの最下部にいる場合は最新のコンテンツをすぐに確認したいと考えます。一度に多くのアイテムが届いた場合、リストはスキップして一番下の最新のアイテムを表示します(つまり、ユーザーがスクロールして戻らない限り、中間にあるアイテムはまったく表示されない可能性があります)。

これらのユースケースをサポートするため、TransformingLazyColumn では reverseLayout = true を設定してレイアウトを反転させることができます。これにより、リストのアンカーが上端から下端に変更されます。

便宜上、reverseLayout = true を設定すると、アイテムの視覚的な順序とスクロール ジェスチャーの方向も反転します。

  • アイテムは下から上に構成されます。つまり、インデックス 0 は画面の下部に表示されます。
  • 上にスクロールすると、インデックスの高いアイテムが表示されます。

逆レイアウトとともにスナップとフリングの動作を追加するには、次のスニペットに示すように、flingBehaviorrotaryScrollableBehavior を組み合わせます。

val columnState = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
ScreenScaffold(scrollState = columnState) { contentPadding ->
    TransformingLazyColumn(
        state = columnState,
        contentPadding = contentPadding,
        reverseLayout = true,
        modifier = Modifier.fillMaxWidth()
    ) {
        items(10) { index ->
            Button(
                label = {
                    Text(
                        text = "Item ${index + 1}"
                    )
                },
                onClick = {},
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ButtonDefaults.minimumVerticalListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            )
        }
        item {
            // With reverseLayout = true, the last item declared appears at the top.
            ListHeader(
                modifier = Modifier
                    .fillMaxWidth()
                    .transformedHeight(this, transformationSpec)
                    .minimumVerticalContentPadding(ListHeaderDefaults.minimumTopListContentPadding),
                transformation = SurfaceTransformation(transformationSpec)
            ) {
                Text("Header")
            }
        }
    }
}

次の画像は、通常のリストと逆順のリストの違いを示しています。

通常のレイアウトの TransformingLazyColumn。Item 1 が一番上に表示され、アイテムが昇順で表示されている。
図 1. コンテンツが上から下まで埋められる標準のリスト レイアウト。
レイアウトが反転した TransformingLazyColumn。一番下に Item 1 が表示され、上に向かって降順でアイテムが表示されている。
図 2. コンテンツが下から上に埋められる反転リスト レイアウト。