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:
- UI = f(state):相同状态输入得到相同 UI 输出;
- 单向数据流:状态从上往下流,事件从下往上传;
- 不可变:数据用
data class+val,状态变化走MutableState/StateFlow; - 重组:状态变化时 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实现。
发布包不会包含它们。
常见坑与最佳实践
- 不写 BOM 直接写版本号:导致 Compose 库之间版本不兼容,运行时
NoSuchMethodError。永远用platform("androidx.compose:compose-bom:...")。 @Composable函数返回值:返回Unit以外的类型会编译错误(除了Modifier/State等白名单)。- 在普通函数里调用 Composable:比如
init {}里调用Text(...),编译器会报 “Composable invocations can only happen”。必须从setContent/ 另一个 Composable 入口。 - 状态用
var不remember:每次重组都被重新初始化,状态丢失。用remember { mutableStateOf(...) }。 - Preview 函数定义在 release 模块:Preview 函数对生产无用,建议放在
debug源集或加@Suppress,避免发布包里残留 dead code。 - 不写 Preview:纯文本很难想象 UI 长什么样,每个组件至少一个 Preview 是 Compose 的“最佳实践”。
setContent中没包 MaterialTheme:颜色 / 字体没默认值,会显示一片黑或异常排版。MaterialTheme是几乎所有 Compose UI 的根。AppCompatActivity+ Compose:能跑但没必要。Compose 自己有MaterialTheme与WindowInsets,纯 Compose Activity 用ComponentActivity即可。- 在
@Composable中起协程:直接viewModelScope.launch { }没问题,但 UI 触发的协程应使用rememberCoroutineScope(),让作用域跟随 Composable 生命周期。 - 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 响应式。把这些布局吃透后,任何复杂界面都能拼出来。