Android TV Jetpack Compose:自定义TvLazyRow焦点跳转至首项
修改Jetpack Compose TV中TvLazyRow的焦点跳转行为
要实现按下向下键时从当前行的任意项跳转到下一行的第一个项,核心是通过自定义焦点导航规则覆盖默认的焦点匹配逻辑。以下是具体实现步骤:
1. 准备每行的焦点请求器
为每个landscape类型的TvLazyRow创建一个FocusRequester,用来控制该行第一个元素的焦点状态。用Map存储每行的请求器,通过行的唯一标识(比如分类名或ID)关联:
val rowFocusRequesters = remember { mutableMapOf<String, FocusRequester>() }
2. 为每行第一个元素绑定焦点请求器
在遍历生成TvLazyRow的逻辑中,为当前行生成唯一Key,获取或创建对应的FocusRequester,并绑定到该行的第一个卡片上:
// 假设数据源列表为resultItems resultItems.forEach { resultItem -> if (resultItem.thumbnailType?.name.equals("landscape", ignoreCase = true) && !resultItem.assets.isNullOrEmpty()) { // 生成行的唯一标识,避免重复 val rowUniqueKey = resultItem.categoryName ?: resultItem.id.toString() val rowFirstItemRequester = rowFocusRequesters.getOrPut(rowUniqueKey) { FocusRequester() } // 原有分类标题代码 TextStyle( text = resultItem.categoryName ?: "", modifier = Modifier.padding(top = dimensionResource(R.dimen.sixteen_dp)), color = colorResource(R.color.white) ) TvLazyRow( pivotOffsets = PivotOffsets(0.0F), modifier = Modifier .padding(top = dimensionResource(R.dimen.eight_dp)) .onFocusChanged { focusState -> } ) { val assets = resultItem.assets.take(10) items(assets) { asset -> val isFirstItem = asset == assets.first() LandScapeCardWithWidth( image = asset.imageLandscape!!, title = asset.name, isViewAll = false, type = asset.type, itemClick = {}, // 修改modifier,添加焦点请求器和自定义焦点规则 modifier = Modifier .run { if (isFirstItem) this.focusRequester(rowFirstItemRequester) else this } .focusProperties { // 重写向下方向的焦点目标 down = { // 找到当前行在数据源中的索引 val currentRowIndex = resultItems.indexOf(resultItem) // 检查是否存在下一行 if (currentRowIndex < resultItems.size - 1) { val nextRow = resultItems[currentRowIndex + 1] // 判断下一行是否是landscape类型且有内容 if (nextRow.thumbnailType?.name.equals("landscape", ignoreCase = true) && !nextRow.assets.isNullOrEmpty()) { val nextRowKey = nextRow.categoryName ?: nextRow.id.toString() // 返回下一行第一个元素的焦点请求器作为目标 rowFocusRequesters[nextRowKey]?.let { FocusRequesterModifierNode(it) } } else { // 下一行不符合条件,保持默认焦点行为 down } } else { // 已经是最后一行,无下一行,保持默认 down } } }, hideTag = asset.hideTag.toString(), contentAvailability = asset.contentAvailability, size = dimensionResource(R.dimen.two_hundred_dp), onFocusChange = { isFocused -> if (isFocused) { assetSynopsys = asset.synopsis languageSize = asset.languages?.size.toString() assetImage = asset.imageTransparent focusedImageUrl = asset.imageLandscape assetRating = asset.viewerRating?.ratingName assetHour = asset.hours assetMin = asset.min } } ) } } } }
关键逻辑说明
- 焦点请求器映射:
rowFocusRequesters存储每行第一个元素的FocusRequester,确保能准确定位到下一行的第一个可聚焦项。 - 自定义焦点规则:通过
focusProperties { down = ... }重写向下方向的焦点目标,按下向下键时直接将焦点导向下一行的第一个元素,而非默认的对应位置项。 - 边界处理:判断当前是否为最后一行、下一行是否符合
landscape类型,避免空指针或无效焦点跳转。
额外注意事项
- 确保
resultItems是稳定列表(比如用remember缓存),避免indexOf出现错误。 - 如果行内容动态变化,需同步更新
rowFocusRequesters中的映射关系,防止焦点请求器失效。
内容的提问来源于stack exchange,提问作者Priyanshu Raj
相关产品推荐
相关产品推荐

