Jetpack Compose 入门:用声明式 UI 写 Android 页面

Jetpack Compose 入门:用声明式 UI 写 Android 页面

背景

过去写 Android 页面,主流方式是 XML 布局加 Activity/Fragment 代码:XML 负责描述控件层级,Kotlin/Java 负责查找 View、绑定数据、处理点击。这套模式稳定,但页面复杂后很容易出现两个问题:一是 UI 结构和状态逻辑分散在不同文件里,阅读成本高;二是状态变化后要手动更新多个 View,遗漏一个就会出现界面不同步。

Jetpack Compose 是 Google 推出的现代 Android UI 工具包,它不再要求你写 XML,而是直接用 Kotlin 函数描述界面。更重要的是,Compose 的核心思想是"状态驱动 UI":页面长什么样由当前状态决定,状态变化后框架自动重新绘制相关区域。对新项目来说,Compose 能显著减少模板代码;对老项目来说,也可以从局部页面、弹窗、列表项开始渐进式接入。

核心概念

Composable 函数

@Composable 标记的函数就是一个 UI 片段。它可以像普通 Kotlin 函数一样接收参数、组合调用,但只能在另一个 Composable 环境中执行。可以把它理解成"用函数声明控件树"。

声明式 UI

传统 View 更偏命令式:先找到按钮,再调用 button.text = ...button.visibility = ...。Compose 更偏声明式:你只描述"当状态为 A 时显示什么,当状态为 B 时显示什么"。状态一变,Compose 会触发重组(Recomposition),自动更新界面。

State 与 remember

remember 用于在重组之间保存对象,mutableStateOf 用于创建可观察状态。只要 Composable 读取了某个 State,当它变化时,相关 UI 就会自动刷新。

Modifier

Modifier 是 Compose 中非常重要的修饰链,用来设置大小、间距、背景、点击、滚动等行为。它不是传统 View 的属性集合,而是一条从左到右顺序生效的链。

Kotlin 代码实战

先确保模块启用了 Compose,并引入基础依赖。新版本 Android Studio 创建 Empty Activity 时可以直接选择 Compose 模板,这里重点看代码结构。

1. 在 Activity 中挂载 Compose

kotlin 复制代码
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MaterialTheme {
                Surface {
                    CounterScreen()
                }
            }
        }
    }
}

setContent {} 取代了过去的 setContentView(R.layout.xxx)。花括号内部就是 Compose 世界,后续页面都从这里组合出来。

2. 写一个简单计数页面

kotlin 复制代码
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp

@Composable
fun CounterScreen() {
    var count by remember { mutableIntStateOf(0) }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(text = "当前计数:$count", fontSize = 24.sp)

        Spacer(modifier = Modifier.height(16.dp))

        Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
            Button(onClick = { count-- }) {
                Text("减一")
            }
            Button(onClick = { count++ }) {
                Text("加一")
            }
        }
    }
}

这段代码里没有 findViewById,也没有手动设置 TextView 文本。count 改变后,读取 countText 会自动刷新,这就是状态驱动 UI 的直观体现。

3. 拆分可复用组件

实际项目不要把所有内容都塞进一个函数。可以把展示区和按钮区拆出来,让页面更容易测试和维护。

kotlin 复制代码
@Composable
fun CounterTitle(count: Int) {
    Text(text = "当前计数:$count", fontSize = 24.sp)
}

@Composable
fun CounterActions(
    onDecrease: () -> Unit,
    onIncrease: () -> Unit
) {
    Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
        Button(onClick = onDecrease) { Text("减一") }
        Button(onClick = onIncrease) { Text("加一") }
    }
}

调用方只传状态和事件,不把内部实现暴露出去:

kotlin 复制代码
@Composable
fun CounterScreen() {
    var count by remember { mutableIntStateOf(0) }

    Column(
        modifier = Modifier.fillMaxSize().padding(24.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        CounterTitle(count = count)
        Spacer(modifier = Modifier.height(16.dp))
        CounterActions(
            onDecrease = { count-- },
            onIncrease = { count++ }
        )
    }
}

4. 列表写法

Compose 中列表通常使用 LazyColumn,它类似 RecyclerView,只渲染当前屏幕附近的内容。

kotlin 复制代码
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items

@Composable
fun MessageList(messages: List<String>) {
    LazyColumn {
        items(messages) { message ->
            Text(
                text = message,
                modifier = Modifier.padding(16.dp)
            )
        }
    }
}

如果列表项有稳定 ID,建议加 key,减少重组和滚动状态错乱:

kotlin 复制代码
items(
    items = messages,
    key = { it }
) { message ->
    Text(text = message, modifier = Modifier.padding(16.dp))
}

避坑指南

不要在 Composable 里直接执行耗时操作。 Composable 可能因为状态变化反复重组,如果在函数体里直接请求网络、读数据库或写文件,就可能被重复执行。副作用逻辑应放到 ViewModel,或使用 LaunchedEffectDisposableEffect 等 Compose 副作用 API。

remember 不是持久化。 remember 只能在当前组合生命周期内保存状态,Activity 重建后可能丢失。需要应对旋转屏幕、进程恢复时,用 rememberSaveable;需要业务状态共享时,用 ViewModel。

Modifier 顺序会影响结果。 Modifier.padding().background()Modifier.background().padding() 的视觉效果不同。前者是先留白再画背景,后者是先画背景再留白。遇到布局异常时,优先检查 Modifier 链顺序。

避免把可变集合直接当状态。 如果用 mutableListOf() 后只执行 add(),Compose 未必知道列表变了。更推荐使用不可变列表:messages = messages + newMessage,或者使用 SnapshotStateList

Composable 函数保持轻量。 一个函数既处理业务、又拼 UI、还发埋点,会很快失控。推荐状态上提:页面函数接收 uiState 和事件回调,业务逻辑放在 ViewModel 中。

Compose 与 XML 可以共存。 老项目没必要一次性重写。可以在 XML 中用 ComposeView 嵌入 Compose,也可以在 Compose 里用 AndroidView 包一段传统 View,逐步迁移更安全。

总结

Jetpack Compose 的入门关键不在于记住多少控件,而是转变思维:界面由状态描述,事件改变状态,状态再驱动界面刷新。掌握 @ComposablerememberStateModifierLazyColumn 后,就已经能写出常见页面。

如果是新项目,可以优先使用 Compose;如果是存量项目,建议从独立页面、设置页、空状态页、列表 Item 开始试点。Compose 降低了 UI 开发的模板成本,但也要求我们更认真地管理状态和副作用。把这两点做好,声明式 UI 会让 Android 页面开发轻很多。

相关推荐
threerocks1 小时前
什么?我连 A2A、MCP 都没学会,现在又来了 AG-UI、A2UI.
前端·aigc·ai编程
牛奶2 小时前
如何自己写一个浏览器插件?
前端·chrome·浏览器
亿元程序员3 小时前
为什么Cocos都4.0了还有人用2.x?
前端
MomentYY3 小时前
AI 到底是“懂”,还是在“猜”?
前端·人工智能·ai编程
鹏毓网络科技3 小时前
Cursor Rules 文件配置实战:3 个隐藏参数让我每月少写 40% 样板代码
前端·github
没烦恼3013 小时前
无痕模式下 HTTP\-First 拦截引发的“页面刷新”误判
前端
文心快码BaiduComate3 小时前
从个人提效到组织提效:Comate辅助构建自我进化的AI研发系统
前端·程序员
hunterandroid3 小时前
Compose 状态管理:remember、rememberSaveable 与状态提升
前端
星栈4 小时前
Dioxus 接数据库最容易写歪的 3 个地方:sqlx + SQLite 怎么接才顺
前端·rust·前端框架
晴虹4 小时前
vue3-scroll-more:横向滚动条-元素或页签过多滚动显示处理的组件
前端·vue.js