如何用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开发的文章了,以后搞车载相关去了.

相关推荐
千里马学框架7 小时前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台7 小时前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone7 小时前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc8 小时前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo10 小时前
Kotlin 协程中的 Job 结构化并发与取消
android
sun00770010 小时前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼11 小时前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone12 小时前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen12 小时前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone12 小时前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui