為電視建立可捲動的版面配置

在電視應用程式中,瀏覽體驗仰賴有效率的焦點導覽。使用標準 Compose Foundation 延遲版面配置,您可以建立高效能的垂直和水平清單,自動處理焦點驅動捲動,讓有效項目保持在檢視畫面中。

針對電視最佳化的預設捲動行為

從 Compose Foundation 1.7.0 開始,標準 Lazy 版面配置 (例如 LazyRow 和 LazyColumn) 內建焦點定位功能。這是建構電視應用程式目錄的建議方式,因為這有助於讓焦點項目保持可見,並以直覺的方式為使用者定位。

如要實作基本的可捲動清單,請使用標準延遲元件。這些元件會自動處理 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 中電視專用的 Lazy 版面配置已淘汰,改用標準 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)