第1章 Compose 声明式 UI 基础与播放器布局

1.1 声明式思维:从“如何做”到“是什么”

传统 Android View 系统采用命令式 UI 开发模式:开发者通过 findViewById() 获取视图对象,再调用 setText()、setOnClickListener() 等方法一步步操作界面。这种模式在简单场景下直观,但当界面状态增多、交互复杂时,视图与状态之间容易产生不一致,代码维护成本急剧上升。

Jetpack Compose 采用声明式 UI 范式:开发者只需描述当前状态下的界面“应该长什么样”,Compose 框架自动处理视图的创建、更新和回收。核心原则是 UI = f(state),即界面是状态的函数。当状态发生变化时,Compose 会智能地重组(Recomposition)受影响的部分,而非整体重绘。

核心差异对比: 命令式 UI 中,开发者手动控制每个 View 的生命周期;声明式 UI 中,开发者只需提供数据和状态,框架自动推导出视图树。
维度命令式(传统 View)声明式(Compose)
状态管理手动同步,易出现遗漏自动观察,单向数据流
视图更新显式调用更新方法声明状态 → 框架自动重组
布局构建XML 布局文件 + 代码引用纯 Kotlin 函数,可组合
调试复杂度断点、Layout Inspector重组计数、Compose 专用工具

1.2 布局组件:构建播放器界面的基本积木

Compose 提供了一系列基础布局组件(Layout Primitive),它们全部以函数形式存在,接收 Modifier 参数进行样式控制。播放器界面通常由以下组件组合而成:

  • Column / Row / Box:线性排列(垂直/水平)和层叠布局。
  • Surface:带背景、圆角、阴影的容器,常用于播放器背景区域。
  • Text:展示标题、时长、进度等信息。
  • Image / AsyncImage:显示视频缩略图或封面。
  • Icon / IconButton:播放/暂停、全屏等控制按钮。
  • Slider:进度条,支持拖动调节。
  • Scaffold:脚手架组件,提供 TopAppBar、BottomBar、FloatingActionButton 等标准壳结构。
推荐方案: 播放器主界面使用 Scaffold 作为外壳,其内部自动处理状态栏、系统导航栏的边距,避免开发者自行适配。

1.3 Scaffold 脚手架:播放器界面骨架

Scaffold 是 Material 3 提供的高级布局容器,它为一个页面预定义了五个插槽:

  • topBar:放置顶部栏(TopAppBar),用于显示视频标题、返回按钮。
  • bottomBar:放置底部栏(BottomAppBar),包含点赞、收藏、评论入口。
  • floatingActionButton:悬浮按钮,可用于快速返回播放。
  • content:主内容区域,播放器视频画面和控制面板放在这里。
  • snackbarHost:用于显示提示消息。

通过 Scaffold,播放器页面的整体结构可以组织为:

Scaffold(
    topBar = { TopAppBar(title = { Text("视频标题") }) },
    bottomBar = { BottomAppBar { /* 点赞、收藏、分享 */ } }
) { innerPadding ->
    // 主内容区域,使用 innerPadding 避开顶栏和底栏
    Column(Modifier.padding(innerPadding)) {
        VideoPlayerView()      // 视频画面
        PlayerControlBar()     // 播放进度、控制按钮
        DescriptionSection()   // 描述、弹幕列表
    }
}

1.4 从概念到代码:播放器布局最小实现

下面的代码片段展示了使用 Scaffold 构建一个视频播放界面雏形,包含顶部标题、底部操作栏和中央的视频区域占位。

@Composable
fun PlayerScreen() {
    Scaffold(
        topBar = {
            TopAppBar(
                title = { Text("Jetpack Compose 播放器") },
                navigationIcon = {
                    IconButton(onClick = { /* 返回 */ }) {
                        Icon(Icons.AutoMirrored.Filled.ArrowBack, "返回")
                    }
                }
            )
        },
        bottomBar = {
            BottomAppBar {
                IconButton(onClick = { /* 收藏 */ }) {
                    Icon(Icons.Filled.FavoriteBorder, "收藏")
                }
                Spacer(Modifier.weight(1f))
                IconButton(onClick = { /* 分享 */ }) {
                    Icon(Icons.Filled.Share, "分享")
                }
            }
        }
    ) { innerPadding ->
        Column(
            Modifier
                .padding(innerPadding)
                .fillMaxSize()
        ) {
            // 视频播放区域(占位)
            Box(
                Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .background(Color.Black),
                contentAlignment = Alignment.Center
            ) {
                Text("视频画面", color = Color.White)
            }
            // 控制栏(待实现)
            PlayerControlBar()
        }
    }
}

@Composable
fun PlayerControlBar() {
    Row(
        Modifier
            .fillMaxWidth()
            .padding(8.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        IconButton(onClick = { /* 播放/暂停 */ }) {
            Icon(Icons.Filled.PlayArrow, "播放")
        }
        Slider(value = 0.3f, onValueChange = {}, modifier = Modifier.weight(1f))
        Text("01:23 / 10:00", modifier = Modifier.padding(start = 8.dp))
    }
}

1.5 调试技巧:使用 Compose 专用工具

  • 重组计数:在 Android Studio 的 Layout Inspector 中开启 Show Recomposition Counts,观察哪些 Composable 重组的频率过高,及时优化。
  • 参数监听:在 Composable 函数入口添加 Log.d 打印参数值,确保状态传递正确。
  • Modifier 链断点:可在 Modifier 链中间插入 .debugLog()(需导入 androidx.compose.ui.util.debugLog)输出当前尺寸和位置。
常见错误: 在 Composable 内部直接调用 View 系统的方法(如 invalidate())会导致性能退化甚至崩溃。应始终使用 remember、LaunchedEffect 等协程原语处理副作用。

1.6 性能与稳定性要点

关注点措施
避免过度重组将不依赖动态状态的 UI 部分提取为 @Composable 并加 @Stable 注解;使用 key 参数帮助 Compose 识别列表项。
控制栏动画使用 animateFloatAsState 代替手动设置值,减少重组次数。
图片加载使用 Coil 或 Glide 的 Compose 扩展,并在 AsyncImage 上设置 contentScale 避免内存浪费。
状态提升将 ViewModel 中的状态通过 collectAsState() 传递,避免在 Composable 内直接持有 MutableState。

1.7 播放器布局结构图

播放器界面布局分解 TopAppBar(视频标题 + 返回按钮) Content 主内容区 视频画面(Box / Surface) 控制栏:Slider + 播放按钮 + 时间 (PlayerControlBar) BottomAppBar(收藏 / 分享)

上面的 SVG 图展示了播放器页面的三层结构:顶栏(TopAppBar)、主内容区(包含视频画面和控制栏)、底栏(BottomAppBar)。在实际项目中,你可以在 Content 区域继续细分出弹幕层、推荐视频列表等子组件,但骨架始终保持一致。