TV 向けにスクロール可能なレイアウトを作成する

テレビアプリの場合、ブラウジング エクスペリエンスは効率的なフォーカスベースのナビゲーションに依存します。標準の Compose Foundation レイジー レイアウトを使用すると、フォーカス駆動型スクロールを自動的に処理してアクティブなアイテムをビュー内に維持する、パフォーマンスの高い縦方向と横方向のリストを作成できます。

テレビ向けに最適化されたデフォルトのスクロール動作

Compose Foundation 1.7.0 以降では、標準の遅延レイアウト(LazyRowLazyColumn など)にフォーカス位置指定機能の組み込みサポートが含まれています。これは、TV アプリのカタログを構築するうえで推奨される方法です。この方法では、ユーザーが注目しているアイテムを直感的に認識し、見つけやすくすることができます。

基本的なスクロール可能なリストを実装するには、標準の遅延コンポーネントを使用します。これらのコンポーネントは、D-pad ナビゲーションを自動的に処理し、フォーカスされたアイテムをビューに表示します。

import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items

@Composable
fun MovieCatalog(movies: List<Movie>) {
    LazyRow {
        items(movies) { movie ->
            MovieCard(
                movie = movie,
                onClick = { /* Handle click */ }
            )
        }
    }
}

BringIntoViewSpec でスクロールの動作をカスタマイズする

デザインで特定の「ピボット」ポイント(たとえば、フォーカスされたアイテムを左端から 30% の位置に保つ)が必要な場合は、BringIntoViewSpec を使用してスクロール動作をカスタマイズできます。これにより、古い pivotOffsets 機能が置き換えられ、フォーカスされたアイテムに合わせてビューポートをスクロールする方法を正確に定義できるようになります。

1. カスタム BringIntoViewSpec を定義する

次のヘルパー コンポーザブルを使用すると、親と子の比率に基づいて「ピボット」を定義できます。parentFraction は、コンテナ内のアイテムの着地点を決定し、childFraction は、そのポイントに揃えるアイテムの部分を決定します。

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PositionFocusedItemInLazyLayout(
    parentFraction: Float = 0.3f,
    childFraction: Float = 0f,
    content: @Composable () -> Unit,
) {
    val bringIntoViewSpec = remember(parentFraction, childFraction) {
        object : BringIntoViewSpec {
            override fun calculateScrollDistance(
                offset: Float,       // Item's initial position
                size: Float,         // Item's size
                containerSize: Float // Container's size
            ): Float {
                // Calculate the offset position of the item's leading edge.
                val initialTargetForLeadingEdge =
                    parentFraction * containerSize - (childFraction * size)
                // If the item fits in the container, and scrolling would cause
                // its trailing edge to be clipped, adjust targetForLeadingEdge
                // to prevent over-scrolling near the end of list.
                val targetForLeadingEdge = if (size <= containerSize &&
                    (containerSize - initialTargetForLeadingEdge) < size) {
                    // If clipped, align the item's trailing edge with the
                    // container's trailing edge.
                    containerSize - size
                } else {
                    initialTargetForLeadingEdge
                }
                // Return scroll distance relative to initial item position.
                return offset - targetForLeadingEdge
            }
        }
    }

    // Apply the spec to all scrollables in the hierarchy
    CompositionLocalProvider(
        LocalBringIntoViewSpec provides bringIntoViewSpec,
        content = content,
    )
}

2. カスタム仕様を適用する

レイアウトをヘルパーでラップして、位置合わせを適用します。これは、カタログのさまざまな行にわたって「一貫したフォーカス ライン」を作成する場合に便利です。

PositionFocusedItemInLazyLayout(
    parentFraction = 0.3f, // Pivot 30% from the edge
    childFraction = 0.5f   // Center of the item aligns with the pivot
) {
    LazyColumn {
        items(sectionList) { section ->
            // This row and its items will respect the 30% pivot
            LazyRow { ... }
        }
    }
}

3. 特定のネストされたレイアウトをオプトアウトする

カスタム ピボットではなく標準のスクロール動作を使用する必要がある特定のネストされたレイアウトがある場合は、DefaultBringIntoViewSpec を指定します。

private val DefaultBringIntoViewSpec = object : BringIntoViewSpec {}

PositionFocusedItemInLazyLayout {
    LazyColumn {
        item {
            // This row will ignore the custom pivot and use default behavior
            CompositionLocalProvider(LocalBringIntoViewSpec provides DefaultBringIntoViewSpec) {
                LazyRow { ... }
            }
        }
    }
}

実際には、空の BringIntoViewSpec を渡すことで、フレームワークのデフォルトの動作が引き継がれます。

TV Foundation から Compose Foundation への移行

androidx.tv.foundation のテレビ専用の遅延レイアウトは、標準の Compose Foundation レイアウトに置き換えられました。

依存関係の更新

build.gradle が次のバージョン 1.7.0 以降を使用していることを確認します。

  • androidx.compose.foundation
  • androidx.compose.runtime

コンポーネントのマッピング

移行するには、インポートを更新し、コンポーネントから Tv 接頭辞を削除します。

サポート終了のテレビ コンポーネント Compose Foundation の代替
TvLazyRow LazyRow
TvLazyColumn LazyColumn
TvLazyHorizontalGrid LazyHorizontalGrid
TvLazyVerticalGrid LazyVerticalGrid
pivotOffsets BringIntoViewSpec(LocalBringIntoViewSpec 経由)