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

Android TV Compose迁移:EPG节目指南实现问题求助

解决方案

一、实现频道栏与节目栏联动垂直滚动

要实现每个频道对应一行节目,且和侧边频道栏同步滚动,核心思路是共享滚动状态,让两个垂直滚动容器(LazyColumn)使用同一个LazyListState,确保滚动位置完全同步。

步骤与代码示例

  1. 定义基础数据类:
data class Channel(
    val id: String,
    val name: String
)

data class Program(
    val id: String,
    val title: String,
    val startTime: Long,
    val duration: Long
)
  1. 构建联动滚动布局:
@Composable
fun EpgScreen(channels: List<Channel>, channelPrograms: Map<String, List<Program>>) {
    // 共享同一个滚动状态,确保两个LazyColumn同步滚动
    val scrollState = rememberLazyListState()

    Row(modifier = Modifier.fillMaxSize()) {
        // 侧边频道栏
        LazyColumn(
            state = scrollState,
            modifier = Modifier.width(120.dp).fillMaxHeight(),
            contentPadding = PaddingValues(vertical = 8.dp)
        ) {
            items(channels) { channel ->
                ChannelItem(channel = channel)
            }
        }

        // 节目内容栏:每个频道对应一行横向滚动的节目
        LazyColumn(
            state = scrollState,
            modifier = Modifier.weight(1f).fillMaxHeight(),
            contentPadding = PaddingValues(vertical = 8.dp)
        ) {
            items(channels) { channel ->
                ProgramRow(programs = channelPrograms[channel.id] ?: emptyList())
            }
        }
    }
}

@Composable
fun ChannelItem(channel: Channel) {
    Text(
        text = channel.name,
        modifier = Modifier
            .fillMaxWidth()
            .height(60.dp) // 固定高度,和节目行高度保持一致
            .padding(horizontal = 16.dp, vertical = 16.dp)
            .focusable(true)
            .focusIndicator(),
        fontSize = 18.sp
    )
}

@Composable
fun ProgramRow(programs: List<Program>) {
    LazyRow(
        modifier = Modifier.height(60.dp).fillMaxWidth(), // 和频道项高度一致
        contentPadding = PaddingValues(horizontal = 8.dp)
    ) {
        items(programs) { program ->
            ProgramItem(program = program)
        }
    }
}

@Composable
fun ProgramItem(program: Program) {
    val durationInMinutes = (program.duration / 60000).toString()
    Text(
        text = "${program.title}\n$durationInMinutes' ",
        modifier = Modifier
            .width(150.dp)
            .height(52.dp)
            .padding(8.dp)
            .focusable(true)
            .focusIndicator(),
        fontSize = 14.sp
    )
}

关键注意点

  • 确保频道项和节目行的高度完全一致,否则滚动时会出现错位
  • 使用同一个LazyListState绑定两个LazyColumn,实现滚动联动

二、修复TV端焦点高亮不显示问题

仅设置focusable(true)不足以显示焦点高亮,需要结合Compose TV的焦点指示符或自定义焦点样式:

方案1:使用官方focusIndicator()修饰符

Compose TV库提供了focusIndicator(),可以快速添加默认焦点高亮效果,直接添加到focusable()之后即可:

Modifier
    .focusable(true)
    .focusIndicator() // 自动添加系统默认焦点框

方案2:自定义焦点高亮样式

如果默认样式不符合需求,可以通过drawBehind自定义焦点框:

Modifier
    .focusable(true)
    .drawBehind {
        if (isFocused) {
            val borderWidth = 2.dp.toPx()
            drawRect(
                color = Color.White,
                style = Stroke(width = borderWidth),
                topLeft = Offset(borderWidth, borderWidth),
                size = size - Size(borderWidth * 2, borderWidth * 2)
            )
        }
    }

关键注意点

  • 确保依赖了Compose TV相关库(如androidx.tv:tv-foundation)
  • 焦点修饰符的顺序:focusable()要在focusIndicator()或自定义绘制修饰符之前

内容的提问来源于stack exchange,提问作者techker

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 23:22:34