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

相关推荐
泥嚎泥嚎1 小时前
【Android】给App添加启动画面——SplashScreen
android·java
全栈派森1 小时前
初见 Dart:这门新语言如何让你的 App「动」起来?
android·flutter·ios
q***98521 小时前
图文详述:MySQL的下载、安装、配置、使用
android·mysql·adb
恋猫de小郭2 小时前
Dart 3.10 发布,快来看有什么更新吧
android·前端·flutter
恋猫de小郭3 小时前
Flutter 3.38 发布,快来看看有什么更新吧
android·前端·flutter
百锦再9 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
会跑的兔子10 小时前
Android 16 Kotlin协程 第二部分
android·windows·kotlin
键来大师10 小时前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
江上清风山间明月12 小时前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
百锦再13 小时前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang