1. Compose 初体验:声明式UI到底是什么?

说实话,我第一次接触 Compose 的时候,心里是有点抗拒的。做了七八年 Android 开发,XML + findViewById 那套流程早就刻在肌肉记忆里了。但当我真正写完第一个 @Composable 函数,看到模拟器上那个 "Hello World" 的时候——嗯,我承认,这感觉确实不一样。

这一章,咱们不聊虚的。直接上手,看看声明式UI到底是个什么玩意儿,跟传统 View 系统比有什么优势,最后再搭环境跑一个 Hello World。

什么是声明式UI?

先问一个问题:你平时怎么写界面?

传统方式是这样的:

  • 先在 XML 里画一个布局
  • 然后在 Activity 里 findViewById
  • 再写一堆 setText、setOnClickListener
  • 数据变了?再手动调用 setText 更新

这叫做「命令式UI」——你告诉系统每一步该做什么。

而声明式UI呢?

你只需要描述界面「应该长什么样」,剩下的交给框架。

核心思想:UI = f(state)。界面是状态的一个函数。状态变了,界面自动重新绘制。

举个例子。你写一个计数器:

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Button(onClick = { count++ }) {
        Text("点击了 $count 次")
    }
}

看到没?没有 findViewById,没有 setText。count 变了,Text 自动更新。这就是声明式的魅力。

我个人习惯:刚开始学 Compose 的时候,别想太多「底层怎么实现的」。先把它当成一个「描述UI的工具」来用,慢慢就理解了。

Compose 与传统 View 系统的对比

我做过一个项目,用传统 View 写了一个复杂的表单页面。大概有 30 多个输入框,各种联动校验。那代码量……啧,光是 findViewById 就写了小一百行。后来用 Compose 重写了一遍,代码量直接砍半。

咱们来做个对比:

对比维度 传统 View 系统 Jetpack Compose
UI 构建方式 XML + 代码 纯 Kotlin 代码
数据更新 手动调用 setText / setVisibility 状态变化自动重组
布局层级 容易嵌套过深,性能下降 扁平化,性能更好
代码可读性 UI 和逻辑分离,但跳来跳去 UI 和逻辑在一起,一目了然
学习曲线 低,但写复杂 UI 很痛苦 初期有点别扭,上手后很爽
预览 Layout Editor 预览 @Preview 注解,实时预览

你想想看,传统方式最大的问题是什么?是「状态和UI不同步」。数据变了,你忘了更新界面,bug 就来了。Compose 从根本上解决了这个问题——状态是唯一的数据源,UI 自动跟着状态走。

我曾经踩过一个坑:在传统 View 项目里,有个页面需要根据用户权限显示/隐藏某些按钮。我写了七八个 if-else 来控制 visibility。后来需求变了,漏改了一个地方,线上出了权限泄露。用 Compose 的话,你只需要在状态里维护一个权限字段,UI 自动响应,根本不会漏。

Compose 的核心架构

为了让你更直观地理解 Compose 的工作方式,我画了一张图:

Compose 声明式UI核心架构 应用状态 State / MutableState 读取 Composable 函数 @Composable fun 生成 界面 UI Text / Button / Image 用户交互 → 更新状态 重组(Recomposition)流程 1. 用户点击按钮 → 状态 count 增加 2. Compose 检测到状态变化 → 标记相关 Composable 为「需要重组」 3. 框架智能地只重组受影响的部分 → 更新 Text 显示新数值 4. 整个过程自动完成,无需手动调用任何更新方法

说白了,Compose 就是一个「状态驱动UI」的框架。你只需要关心状态怎么变,UI 会自动跟着变。这跟传统 View 系统那种「手动同步」的思路完全不同。

环境搭建

好,理论说完了,咱们来点实际的。搭环境。

你需要准备的东西不多:

  1. Android Studio —— 推荐 Arctic Fox 2020.3.1 以上版本
  2. JDK 11+ —— 这个一般 Android Studio 自带
  3. Kotlin 插件 —— 通常也是自带的

我建议:直接用 Android Studio 最新稳定版。别折腾老版本,Compose 对 IDE 版本有要求,版本太低连预览都跑不了。

创建项目的步骤:

  1. 打开 Android Studio,点击 "New Project"
  2. 选择 "Empty Compose Activity" 模板
  3. 填写项目名称、包名、保存路径
  4. Minimum SDK 选 API 21 或以上
  5. 点击 Finish,等 Gradle 同步完成

同步完成后,你会看到项目里已经有一个 MainActivity.kt 文件。打开它,代码大概是这样的:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            HelloWorldTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    Greeting("Android")
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    HelloWorldTheme {
        Greeting("Android")
    }
}

看到 setContent 了吗?这就是 Compose 的入口。它替代了传统 Activity 里的 setContentView(R.layout.activity_main)

第一个 Hello World

咱们把默认的 "Hello Android" 改成 "Hello World"。顺便加点料:

@Composable
fun Greeting(name: String) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Hello $name!",
            fontSize = 28.sp,
            fontWeight = FontWeight.Bold,
            color = Color(0xFF1976D2)
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Text(
            text = "欢迎来到 Compose 的世界",
            fontSize = 16.sp,
            color = Color.Gray
        )
    }
}

然后运行一下。嗯,看到模拟器上那个蓝色加粗的 "Hello World" 了吗?这就是你用 Compose 写的第一行 UI。

关键点:注意看 ColumnTextSpacer 这些组件。它们都是函数,不是类。在 Compose 里,一切 UI 都是函数调用。你不需要 new 一个对象,也不需要设置属性——直接传参数就行。

避坑指南

我刚开始用 Compose 的时候,遇到过几个问题,分享给你:

  • 预览不显示?检查一下 build.gradle 里有没有加 compose 相关的依赖,还有 kotlin 版本是不是 1.5.0 以上。
  • 运行报错?看看 Gradle 版本是不是太老了。Compose 对 Gradle 版本有要求,建议用 7.0 以上。
  • 重组太频繁?这个后面会讲,记住一点:不要在 Composable 函数里做耗时操作。

我曾经犯过一个低级错误:setContent 外面调用了 Compose 的组件。结果编译报错,提示 "Composable invocations can only happen from the context of a Composable function"。记住,所有 @Composable 注解的函数,只能在另一个 @Composable 函数里调用,或者在 setContent 块里调用。

好了,这一章的内容就到这里。你学会了什么是声明式UI,知道了 Compose 和传统 View 系统的区别,也亲手跑了一个 Hello World。下一章咱们会深入 Compose 的布局系统,看看那些好用的组件到底怎么用。


公众号:蓝海资料掘金营,微信deep3321