对于 TV 应用,浏览体验依赖于高效的基于焦点的导航。 使用标准 Compose Foundation 延迟布局,您可以创建高性能的垂直和水平列表,这些列表会自动处理焦点驱动的滚动,以使活动项保持在视图中。
针对 TV 优化的默认滚动行为
从 Compose Foundation 1.7.0 开始,标准延迟布局(如 LazyRow 和 LazyColumn)内置了对焦点定位功能的支持。这是为 TV 应用构建目录的推荐方式,因为它有助于使焦点项保持可见,并以直观的方式定位给用户。
如需实现基本的滚动列表,请使用标准延迟组件。这些组件会自动处理方向键导航,并将焦点项显示在视图中。
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
为了支持标准 Compose Foundation 布局,androidx.tv.foundation 中的 TV 特定延迟布局已被弃用。
依赖项更新
验证 build.gradle 是否对以下项使用 1.7.0 或更高版本:
androidx.compose.foundationandroidx.compose.runtime
组件映射
如需迁移,请更新导入并移除组件中的 Tv 前缀:
| 已弃用的 TV 组件 | Compose Foundation 替换项 |
|---|---|
| TvLazyRow | LazyRow |
| TvLazyColumn | LazyColumn |
| TvLazyHorizontalGrid | LazyHorizontalGrid |
| TvLazyVerticalGrid | LazyVerticalGrid |
| pivotOffsets | BringIntoViewSpec(通过 LocalBringIntoViewSpec) |