如何用Compose TV写电视桌面

写在前面

Compose TV 最近出来已经有一段时间,对电视开发支持的非常好,比如标题,横向/纵向列表,焦点等.

下图为最终效果成品。

Demo源码地址

整体UI框架搭建

标题(TabRow) + NatHost(内容切换) + 内容(TvLazyColumn)

标题-TabRow

kotlin 复制代码
val tabs = listof("我的", "影视", "应用")

TabRow(
    selectedTabIndex = selectedTabIndex,
    indicator = { tabPositions, isActivated ->
        // 移动的白色色块
        TopBarMoveIndicator(...
    }
) {
    tabs.forEachIndexed { index, title ->
        Tab(
            // colors设置了 默认,上焦,选中的颜色
            colors = TabDefaults.pillIndicatorTabColors(
                        contentColor = Color.White,
                        focusedContentColor = Color.Black,
                        selectedContentColor = Color.White,
                    )
            ...
        ) {
            Text(...)
        }
    }
}

移动的白色色块,这里只是我写的Demo,都是可以自定义的.

kotlin 复制代码
fun TopBarMoveIndicator(
    currentTabPosition: DpRect,
    isFocused: Boolean
) {
    val width by animateDpAsState(targetValue = currentTabPosition.width, label = "width")
    val height = if (isFocused) currentTabPosition.height else 2.dp
    val leftOffset by animateDpAsState(targetValue = currentTabPosition.left, label = "leftOffset")
    // 有焦点的时候,是矩形,无焦点的时候,是下划线.
    val moveShape = if (isFocused) ShapeDefaults.ExtraLarge else ShapeDefaults.ExtraSmall

	Box(
        modifier = Modifier
            .fillMaxWidth()
            .wrapContentSize(Alignment.BottomStart)
            .offset(leftOffset, currentTabPosition.top)
            .width(width)
            .height(height)
            .background(color = Color.White, shape = moveShape)
            .zIndex(-1f)
    )
}

NatHost(内容切换) + 内容(TvLazyColumn)

内容切换

NatHost 功能类似 ViewPager,对 "我的","影视","应用" 几个 页面内容进行切换.

kotlin 复制代码
NavHost(
    ...
	builder = {
    	composable(...) { // 我的
        	// 我的野蛮
        }
        composable(...) {// 影视
        	// 影视页面
        }
        composable(...) { // 应用
            // 应用页面
        }
    }
)

内容布局

TvLazyColumn 与 LazyColumn 功能是差不多的,纵向布局,就不过多赘述,具体看谷歌的开发文档,网上相关视频教程 或 看DEMO源码.

kotoin 复制代码
TvLazyColumn(
    ...
) {
    item {
        ImmersiveList(...) // 沉浸式列表
    }
    item {
        TvLazyRow(...) // 热门推荐
    }
    item {
        TvLazyRow(...)
    }
    item {
        TvLazyRow(...) // 豆瓣高分
        TvLazyRow(...) 
    }
    item {
        TvLazyRow(...) // 预热抢先看
    }
        ... ...
}

TvLazyColumn的相关参数,记住这个参数 pivotOffsets,它是设置滚动的位置的,比如设置滚动一直在中间位置.

kotlin 复制代码
fun TvLazyColumn(
    modifier: Modifier = Modifier,
    state: TvLazyListState = rememberTvLazyListState(),
    contentPadding: PaddingValues = PaddingValues(0.dp),
    reverseLayout: Boolean = false,
    verticalArrangement: Arrangement.Vertical =
        if (!reverseLayout) Arrangement.Top else Arrangement.Bottom,
    horizontalAlignment: Alignment.Horizontal = Alignment.Start,
    userScrollEnabled: Boolean = true,
    pivotOffsets: PivotOffsets = PivotOffsets(),
    content: TvLazyListScope.() -> Unit
)

TvLazyRow + Item

TvLazyColumn 每行又包含了 TvLazyRow 横向布局 (如果是固定的几个,可以用 Row。

自定义的布局可以用 Surface 包含的,几个关键属性, Scale(放大),Border(边框),Glow(阴影)。

kotlin 复制代码
TvLazyRow(...) {
	items(...) { ...
		Surface(
			onClick = {//点击事件}
            scale = ClickableSurfaceDefaults.scale(...),
            border = ClickableSurfaceDefaults.border(...),
        	glow = ClickableSurfaceDefaults.glow(...)
		) {
                    // 你自定义的卡片内容,比如 图片(AsyncImage) + 文本(Text)
		}
	}
}

我Demo里面用的是 谷歌提供的一个包含 图片+文本的控件 StandardCardLayout。

ImmersiveList 沉浸式列表 有点类似 爱奇艺,腾讯,哔哩哔哩等电视应用这种列表.

kotlin 复制代码
ImmersiveList(
        modifier = Modifier.onGloballyPositioned { currentYCoord = it.positionInWindow().y },
        background = {
            // 背景图片内容
        }
) {
    // 布局内容
    // 大标题 + 详情
    // TvLazyRow
}

TV其它控件推荐

Carousel 轮播界面

TvLazyVerticalGrid/TvLazyHorizontalGrid

ModalNavigationDrawer抽屉式导航栏

ListItem

分辨率适配

TV开发涉及分辨率适配问题,Compose 也能很简单的处理此问题,无论你在1920x1080,还是1280x720等分辨率下,无缝切换,毫无压力.

kotlin 复制代码
val displayMetrics = LocalContext.current.resources.displayMetrics
val fontScale = LocalDensity.current.fontScale
val density = displayMetrics.density
val widthPixels = displayMetrics.widthPixels
val widthDp = widthPixels / density
val display = "density: $density\nwidthPixels: $widthPixels\nwidthDp: $widthDp"
KLog.d("display:$display")
CompositionLocalProvider(
    LocalDensity provides Density(
        density = widthPixels / 1920f,
        fontScale = fontScale
    )
) {
    // 我们写的Compose主界面布局
}

参考资料

What's new with TV and intro to Compose

Android TV 上使用 Jetpack Compose

Compose TV官方设计文档

JetStreaamCompose TV demo

Compose TV demo

写在后面

近几年Android推出了很多东西,我的心尖尖是 MVI,flow(完爆Rxjava),Compose>>>

TV开发的发展,一开始是 RecycleView,要去解决焦点,优化等问题,后来是Leanback,到现在的Compose TV(开发速度提升了很多很多).

我也真的很喜欢Compose的写法,简单明了,强烈推荐Compose TV开发电视,我相信谷歌,能将Compose性能优化的越来越好.

最后一篇TV开发的文章了,以后搞车载相关去了.

相关推荐
恋猫de小郭40 分钟前
Flutter 大小核优化又被回滚,然后又开新提案?
android·前端·flutter
Kapaseker43 分钟前
领导问我 Kotlin 的 Nothing 是什么?
android·kotlin
韩曙亮1 小时前
【错误记录】安卓 adb 调试 5037 端口占用排查 ( adb 的三层结构 | 找出本机占用 5037 的进程 )
android·数据库·adb·安卓·错误记录
三少爷的鞋2 小时前
不改变异步本质,只改变代码组织:Telegram 项目的 Kotlin 协程实践
android
好的佩奇11 小时前
蓝牙之广播与扫描
android·flutter·蓝牙
传奇开心果编程13 小时前
【Jetpack Compose娓娓道来】第36课:焦点管理与键盘输入——让遥控器、键盘和输入法都“找得到北”
android·kotlin·移动开发·交互·android jetpack·ui框架·声明式ui
hai_android14 小时前
AudioTrack 深度解析:从原理到实战
android·java·kotlin
红尘伴蝶舞14 小时前
从秒开优化到连环崩溃:深入剖析 ViewPager2 在常驻 DialogFragment 中的复用陷阱与自愈方案
android·kotlin
巫山老妖19 小时前
Android 应用架构演进:MVC / MVP / MVVM / MVI (2026)
android·android jetpack
木头左1 天前
【架构设计】教育类 App 服务端化改造:AI 上移、题库分层与个人题库归属
android·架构设计·系统设计·后端开发·数据库设计