You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 20:55:18