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

相关推荐
500了4 小时前
Kotlin基本知识
android·开发语言·kotlin
人工智能的苟富贵5 小时前
Android Debug Bridge(ADB)完全指南
android·adb
小雨cc5566ru10 小时前
uniapp+Android面向网络学习的时间管理工具软件 微信小程序
android·微信小程序·uni-app
bianshaopeng11 小时前
android 原生加载pdf
android·pdf
hhzz11 小时前
Linux Shell编程快速入门以及案例(Linux一键批量启动、停止、重启Jar包Shell脚本)
android·linux·jar
火红的小辣椒12 小时前
XSS基础
android·web安全
勿问东西14 小时前
【Android】设备操作
android
五味香14 小时前
C++学习,信号处理
android·c语言·开发语言·c++·学习·算法·信号处理
图王大胜16 小时前
Android Framework AMS(01)AMS启动及相关初始化1-4
android·framework·ams·systemserver
工程师老罗18 小时前
Android Button “No speakable text present” 问题解决
android