A
第 44 章KOTLIN35 分钟

Jetpack Compose 入门与环境搭建

Compose 与传统 View 的范式差异、依赖配置与 Compose BOM、@Composable 与函数式 UI、Preview 注解、Greeting 示例与 setContent 接入 Activity、调试技巧

学习目标

  • 理解 Compose 与传统 View 体系的核心差异
  • 完成 Gradle 依赖配置与 Compose BOM 的使用
  • 掌握 @Composable 注解、函数式 UI 与不可变性思维
  • 使用 @Preview 写可预览的组件并接入 Activity 的 setContent
  • 了解 Compose 中常见调试技巧

学习目标

Jetpack Compose 是 Google 在 2021 年发布的 Android 现代 UI 工具包,它用 Kotlin 把“声明式 UI”带进了 Android 官方生态。从这一章起我们将用 Compose 替代 XML + ViewBinding,把 UI 写成“数据 → 树”的纯函数。

本章假设你了解 Activity 与生命周期,但不要求你精通 Java Views 模式。读完本章你将能够:

  • 解释 Compose 与传统 View 体系的核心差异;
  • 在 build.gradle 中正确配置 Compose BOM 与编译器选项;
  • 写出第一个 @Composable 函数并在 @Preview 中预览;
  • 通过 setContent 把 Composable 接入 Activity;
  • 用 Layout Inspector、Recomposition 高亮等工具调试 Composable。

Compose 是什么

1. 一句话定义

Jetpack Compose 是基于 Kotlin 的 Android 声明式 UI 工具包。它没有 XML、没有 View / ViewGroup、没有 findViewById;UI 用 Kotlin 函数描述,编译器把这些函数变成 Android Canvas 上的真实控件。

2. 声明式 vs 命令式

  • 命令式(传统 View):你描述“步骤”——findViewById → setText(...) → setVisibility(...)。状态变化后手动同步到 View。
  • 声明式(Compose):你描述“UI 长什么样”——Text(text = state.name)。状态变化时 Compose 自动重新调用这个函数刷新。

类比:传统 View 是“我教你每步怎么做菜”,Compose 是“我要这道菜的样子,你照着做”。

3. 与传统 View 的对比

维度 传统 View Jetpack Compose
语言 XML + Java/Kotlin Kotlin(不能 Java)
状态同步 手动 setText / setVisibility 自动重组(recomposition)
复用 自定义 View / include @Composable 函数组合
列表 RecyclerView + Adapter LazyColumn 一段代码
主题 themes.xml / styles.xml MaterialTheme Composable
预览 Layout Editor @Preview 注解
调试 Layout Inspector Layout Inspector + Recomposition 计数

4. 函数式 UI 思维

写 Compose 的心智模型类似 React:

  1. UI = f(state):相同状态输入得到相同 UI 输出;
  2. 单向数据流:状态从上往下流,事件从下往上传;
  3. 不可变:数据用 data class + val,状态变化走 MutableState / StateFlow;
  4. 重组:状态变化时 Compose 自动重新调用依赖它的 Composable。

依赖配置

1. Compose BOM:版本统一管理

Compose 由几十个库组成(compiler、runtime、ui、material3、foundation、animation…),每个都独立发版。手动管理版本噩梦,BOM(Bill of Materials) 把所有相关库版本锁到一份兼容的版本组合,你只需写 BOM 版本:

// build.gradle.kts (app)
android {
    buildFeatures {
        compose = true                  // 启用 Compose
    }
    composeOptions {
        kotlinCompilerExtensionVersion =
            "1.5.14"                    // 与 Kotlin 版本对应
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}

dependencies {
    // Compose BOM:版本协调中心
    val composeBom = platform("androidx.compose:compose-bom:2024.10.00")
    implementation(composeBom)
    androidTestImplementation(composeBom)

    // 不需要写版本号
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    implementation("androidx.activity:activity-compose:1.9.3")

    // Debug 依赖:Layout Inspector 与 Preview 桩
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest")
}

BOM 版本与 Kotlin 版本必须对应:Kotlin 1.9.22 用 Compose Compiler 1.5.8,Kotlin 2.0+ 用 Compose Compiler 2.0+,建议直接用 Kotlin 2.0 + Compose Compiler Gradle Plugin。

2. Kotlin 2.0 的 Compose Compiler 插件

Kotlin 2.0 起 Compose 编译器从 Kotlin 编译器拆出,独立成 Gradle 插件:

// 根 build.gradle.kts
plugins {
    id("com.android.application") version "8.5.2" apply false
    id("org.jetbrains.kotlin.android") version "2.0.20" apply false
    id("org.jetbrains.kotlin.plugin.compose") version "2.0.20" apply false
}

// app/build.gradle.kts
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("org.jetbrains.kotlin.plugin.compose")  // 替代 composeOptions
}

android {
    buildFeatures { compose = true }
    // 不再需要 composeOptions 块
}

3. minSdk 要求

Compose 要求 minSdk = 21(Android 5.0)以上。低于 21 的项目需先提升 minSdk 或放弃 Compose。

4. Java 17

Android Gradle Plugin 8.5 起 JDK 17 是构建最低要求。在 gradle.properties 中:

org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
kotlin.code.style=official

第一个 @Composable

1. @Composable 注解

@Composable 是 Compose 的核心注解,它告诉编译器:“这个函数描述了一个 UI 片段,请帮我转成节点树”。

import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

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

注意:

  • @Composable 函数返回 Unit,不能有返回值(除了 State / Modifier 等白名单类型);
  • 函数名首字母大写是约定(PascalCase);
  • 参数建议用 val(不可变);
  • 不能在普通函数中调用 @Composable 函数,只能在另一个 @Composable 或 Compose runtime 上下文中。

2. 函数式 UI 思维

Greeting 就是一个普通 Kotlin 函数:它接收数据,输出 UI。组合时:

@Composable
fun Profile(name: String, age: Int) {
    Column {
        Greeting(name)
        Text(text = "Age: $age")
    }
}

Profile 是 Greeting 与 Text 的组合。复杂 UI 都是用这种方式一层层拼出来。

3. 状态驱动重组

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }      // 状态
    Button(onClick = { count++ }) {                  // 事件
        Text("点击 $count 次")
    }
}

点击按钮时 count 变化,Compose 看到状态依赖变化,自动重新调用 Counter 与 Text 来刷新屏幕。这就是 recomposition。下章会更深入讲状态。

4. remember:跨越重组保留值

@Composable 函数可能被多次调用(重组),普通局部变量会丢失。remember 在重组间保留值:

@Composable
fun RandomOnce(): Int {
    val n = remember { (1..100).random() }      // 重组时不变
    Text("$n")
}

不用 remember 的话每次重组都会重新算。

@Preview 注解

@Preview 让你在 Android Studio 中不运行就能看到 Composable 渲染结果,类似布局编辑器。

import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter

@Preview(showBackground = true, name = "默认 Greeting")
@Composable
fun GreetingPreview() {
    Greeting(name = "Compose")
}

@Preview(
    showBackground = true,
    widthDp = 320,
    heightDp = 640,
    name = "窄屏 Greeting",
)
@Composable
fun GreetingPreviewNarrow() {
    Greeting(name = "窄屏")
}

要点:

  • Preview 函数必须是无参 @Composable;
  • 不传状态参数,把数据写死在函数内;
  • 不影响 release 包(被 debugImplementation 范围隔离);
  • 可以同时写多个 @Preview 模拟不同尺寸 / 主题 / 深色模式;
  • 可以用 @PreviewParameter 提供多组预定义数据批量预览。

Android Studio 中 Preview 面板在右上角,写完代码会自动刷新,建议 每个 Composable 都配 Preview,方便回归测试 UI 样式。

接入 Activity:setContent

ComponentActivity 扩展函数 setContent 是 Compose 与传统 Activity 的连接点:

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 {
                    Greeting(name = "Android")
                }
            }
        }
    }
}

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

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    Greeting(name = "Preview")
}

要点:

  • 继承 ComponentActivity 而不是 AppCompatActivity(如果纯 Compose 就不需要 AppCompat 主题);
  • setContent { } 块是 Compose 的入口,相当于传统 setContentView(R.layout.xxx);
  • 顶层用 MaterialTheme 包一层,让所有子组件拿到统一的颜色 / 字体;
  • Surface 提供背景与 WindowInsets 处理。

Fragment 中使用

Fragment 里也可以用 ComposeView 嵌入 Compose:

class ProfileFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setViewCompositionStrategy(
                ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
            )
            setContent {
                MaterialTheme { Profile(name = "Alice") }
            }
        }
    }
}

混合迁移到 Compose 的项目里常用这种方式逐屏替换。

Compose 与传统 View 互操作

Compose 不是“非此即彼”,可以渐进迁移:

1. Compose 嵌入传统布局

XML 中放一个 ComposeView:

<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
findViewById<ComposeView>(R.id.compose_view).setContent {
    MaterialTheme { Greeting("from XML") }
}

2. 传统 View 嵌入 Compose

用 AndroidView 在 Composable 中嵌入任何 View:

@Composable
fun LegacyWebView(url: String) {
    AndroidView(
        factory = { ctx -> WebView(ctx).apply { settings.javaScriptEnabled = true } },
        update = { webView -> webView.loadUrl(url) },
    )
}

factory 仅在第一次创建 View 时调用,update 在参数变化时重新调用。

调试技巧

1. Layout Inspector

Android Studio 的 Layout Inspector 适配了 Compose:可以选中屏幕上任意 Composable 节点,看它的层级、参数与测量尺寸。打开方式:View → Tool Windows → Layout Inspector。

2. Recomposition 计数

性能问题第一怀疑对象就是“重组过多”。Layout Inspector 中开 Show recomposition counts,每个 Composable 会显示被重组次数。如果某个节点重组次数飙升,通常说明:

  • 状态写在了不该写的地方;
  • 参数是 List 等不稳定类型未加 @Stable / @Immutable;
  • lambda 没用 remember 缓存。

3. Modifier.semantics 与可访问性

Modifier.semantics {
    contentDescription = "提交按钮"
}

为 Composable 添加无障碍标签,TalkBack 会读出来。

4. 日志与 println

Compose 中可以正常用 Log.d 与 println。在 @Composable 函数开头加一句 Log.d("Recompose", "Counter"),能直观看到重组频率。

5. compose-ui-tooling 的 Preview 桩

debugImplementation("androidx.compose.ui:ui-tooling") 与 ui-test-manifest 提供:

  • @Preview 渲染;
  • Layout Inspector Compose 视图;
  • 测试时的 ComponentActivity 实现。

发布包不会包含它们。

常见坑与最佳实践

  1. 不写 BOM 直接写版本号:导致 Compose 库之间版本不兼容,运行时 NoSuchMethodError。永远用 platform("androidx.compose:compose-bom:...")。
  2. @Composable 函数返回值:返回 Unit 以外的类型会编译错误(除了 Modifier / State 等白名单)。
  3. 在普通函数里调用 Composable:比如 init {} 里调用 Text(...),编译器会报 “Composable invocations can only happen”。必须从 setContent / 另一个 Composable 入口。
  4. 状态用 var 不 remember:每次重组都被重新初始化,状态丢失。用 remember { mutableStateOf(...) }。
  5. Preview 函数定义在 release 模块:Preview 函数对生产无用,建议放在 debug 源集或加 @Suppress,避免发布包里残留 dead code。
  6. 不写 Preview:纯文本很难想象 UI 长什么样,每个组件至少一个 Preview 是 Compose 的“最佳实践”。
  7. setContent 中没包 MaterialTheme:颜色 / 字体没默认值,会显示一片黑或异常排版。MaterialTheme 是几乎所有 Compose UI 的根。
  8. AppCompatActivity + Compose:能跑但没必要。Compose 自己有 MaterialTheme 与 WindowInsets,纯 Compose Activity 用 ComponentActivity 即可。
  9. 在 @Composable 中起协程:直接 viewModelScope.launch { } 没问题,但 UI 触发的协程应使用 rememberCoroutineScope(),让作用域跟随 Composable 生命周期。
  10. minSdk < 21:Compose 不支持,需要先提升 minSdk 才能引入。

章节小结

  • Compose 是 Kotlin 声明式 UI 工具包:UI = f(state),状态变化触发自动重组;
  • 用 Compose BOM 统一管理版本,Kotlin 2.0 起 Compose Compiler 是独立 Gradle 插件;
  • @Composable 注解标记“描述 UI 的函数”,函数名 PascalCase、参数 val、返回 Unit;
  • @Preview 写可预览组件,配 showBackground / widthDp 等参数模拟不同尺寸;
  • setContent { } 是 Compose 进入 Activity 的入口,根节点用 MaterialTheme;
  • Fragment / XML 互操作通过 ComposeView 与 AndroidView 双向打通,支持渐进迁移;
  • 调试靠 Layout Inspector(含重组计数)+ remember + Modifier.semantics。

下一章预告

下一章我们深入 Compose 布局系统:Column / Row / Box 三大容器、Modifier 链式调用(padding / background / size / clickable)、Spacer、ConstraintLayout Compose、Scaffold 顶 / 底 / FAB 结构、verticalScroll 与 BoxWithConstraints 响应式。把这些布局吃透后,任何复杂界面都能拼出来。