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

相关推荐
Frank_HarmonyOS8 小时前
Android MVVM(Model-View-ViewModel)架构
android·架构
新子y12 小时前
【操作记录】我的 MNN Android LLM 编译学习笔记记录(一)
android·学习·mnn
lincats14 小时前
一步一步学习使用FireMonkey动画(1) 使用动画组件为窗体添加动态效果
android·ide·delphi·livebindings·delphi 12.3·firemonkey
想想吴15 小时前
Android.bp 基础
android·安卓·android.bp
bianshaopeng20 小时前
Android studio gradle 下载不下来
ide·android studio
写点啥呢21 小时前
Android为ijkplayer设置音频发音类型usage
android·音视频·usage·mediaplayer·jikplayer
coder_pig1 天前
🤡 公司Android老项目升级踩坑小记
android·flutter·gradle
死就死在补习班1 天前
Android系统源码分析Input - InputReader读取事件
android
死就死在补习班1 天前
Android系统源码分析Input - InputChannel通信
android
死就死在补习班1 天前
Android系统源码分析Input - 设备添加流程
android