Wear OS 向け Compose でのリスト


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

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

推奨されるスケーリングとスクロールのエフェクトを適用するには:

  1. Modifier.transformedHeight を使用して、アイテムが画面をスクロールするときに高さの変化を Compose で計算できるようにします。
  2. transformation = SurfaceTransformation(transformationSpec) を使用して、アイテム コンテンツの縮小などの視覚効果を適用します。
  3. `Text` など、` transformation` をパラメータとして受け取らないコンポーネントには、カスタムの `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) に設定します。 物理的なリューズまたはベゼルを使用する際に一貫したエクスペリエンスを実現するには、rotaryScrollableBehaviorRotaryScrollableDefaults.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.コンテンツが下から上に埋められる反転したリスト レイアウト。