🤖 Android jetpack compose用法总结

Android Jetpack Compose 是 Google 推出的用于构建原生 Android UI 的现代工具包。它使用更少的代码、强大的工具和直观的 Kotlin API,简化并加速了 Android 上的 UI 开发。

1. 核心概念

声明式 UI

Compose 使用声明式 API,这意味着你只需要描述 UI 应该是什么样子,Compose 会负责处理渲染和更新。

@Composable 注解

所有 Compose UI 函数都必须带有 @Composable 注解。

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

2. 基础组件

Compose 提供了一系列开箱即用的 Material Design 组件。

  • Text: 显示文本。
  • Button: 按钮交互。
  • Image: 显示图片。
  • TextField: 文本输入框。
  • Icon: 显示图标。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Composable
fun BasicComponentsDemo() {
Column {
Text("这是一个文本")
Button(onClick = { /* 点击事件 */ }) {
Text("点击我")
}
TextField(
value = "",
onValueChange = { /* 更新逻辑 */ },
label = { Text("输入框") }
)
}
}

3. 布局 (Layouts)

标准布局

  • Column: 垂直排列子元素 (类似 LinearLayout vertical)。
  • Row: 水平排列子元素 (类似 LinearLayout horizontal)。
  • Box: 堆叠子元素 (类似 FrameLayout)。
1
2
3
4
5
6
7
8
9
10
@Composable
fun LayoutDemo() {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(modifier = Modifier.size(50.dp).background(Color.Red))
Column(modifier = Modifier.padding(start = 8.dp)) {
Text("标题", fontWeight = FontWeight.Bold)
Text("副标题", style = MaterialTheme.typography.bodySmall)
}
}
}

列表 (Lists)

使用 LazyColumnLazyRow 来处理大量数据,相当于 RecyclerView。

1
2
3
4
5
6
7
8
@Composable
fun ListDemo(items: List<String>) {
LazyColumn {
items(items) { item ->
Text(text = item, modifier = Modifier.padding(16.dp))
}
}
}

Scaffold

Scaffold 实现了基本的 Material Design 布局结构,如 TopBar, BottomBar, FloatingActionButton, Drawer 等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Composable
fun ScaffoldDemo() {
Scaffold(
topBar = { TopAppBar(title = { Text("首页") }) },
floatingActionButton = {
FloatingActionButton(onClick = {}) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
}
) { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
Text("内容区域")
}
}
}

4. 修饰符 (Modifiers)

Modifier 是 Compose 的核心,用于修饰 Composable 的大小、布局、行为和外观。

  • 布局: fillMaxSize(), width(), height(), padding().
  • 行为: clickable(), scrollable().
  • 外观: background(), border(), clip().

注意: Modifier 的顺序很重要。

1
2
3
4
5
6
7
8
Text(
"Hello",
modifier = Modifier
.padding(10.dp) // 外边距
.background(Color.Gray) // 背景色
.padding(10.dp) // 内边距 (在背景色内部)
.clickable { } // 点击事件
)

5. 状态管理 (State Management)

remember & mutableStateOf

  • mutableStateOf: 创建一个可观察的状态。
  • remember: 在重组 (Recomposition) 期间保存状态。
1
2
3
4
5
6
7
8
9
@Composable
fun Counter() {
// var count by remember { mutableStateOf(0) } // 需要 import getValue/setValue
val count = remember { mutableStateOf(0) }

Button(onClick = { count.value++ }) {
Text("点击次数: ${count.value}")
}
}

状态提升 (State Hoisting)

将状态移动到调用者,使组件变为无状态 (Stateless),便于复用和测试。

1
2
3
4
5
6
@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit) {
Button(onClick = onIncrement) {
Text("Count: $count")
}
}

ViewModel 集成

通常使用 ViewModel 来管理屏幕级别的状态。

1
2
3
4
5
6
7
8
9
10
class MyViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}

@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
// ...
}

6. 副作用 (Side Effects)

在 Composable 中处理非 UI 逻辑(如网络请求、定时器)。

  • LaunchedEffect: 在协程作用域中运行挂起函数。
  • DisposableEffect: 需要清理的副作用(如注册/注销监听器)。
  • SideEffect: 将 Compose 状态发布到非 Compose 代码。
1
2
3
4
5
6
7
8
@Composable
fun TimerScreen() {
val currentOnTimeout by rememberUpdatedState(onTimeout)
LaunchedEffect(Unit) {
delay(1000)
currentOnTimeout()
}
}

7. 互操作性 (Interoperability)

Compose in View (在 XML 中使用 Compose)

使用 ComposeView

1
2
3
4
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
1
2
3
4
5
composeView.setContent {
MaterialTheme {
Greeting("Android")
}
}

View in Compose (在 Compose 中使用 View)

使用 AndroidView

1
2
3
4
5
6
7
8
@Composable
fun MapViewContainer() {
AndroidView(factory = { context ->
MapView(context).apply {
// 初始化 View
}
})
}

8. 进阶用法 (Advanced Usage)

动画 (Animation)

Compose 提供了强大的动画 API。

  • 简单值动画: animateFloatAsState, animateColorAsState
  • 可见性动画: AnimatedVisibility
  • 内容切换: AnimatedContent
1
2
3
4
var visible by remember { mutableStateOf(true) }
AnimatedVisibility(visible = visible) {
Text("我是可隐藏的文本")
}

导航 (Navigation)

使用 Navigation Compose 库管理页面跳转。

1
2
3
4
5
6
7
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen(navController) }
composable("details/{id}") { backStackEntry ->
DetailScreen(backStackEntry.arguments?.getString("id"))
}
}

CompositionLocal

用于在组件树中隐式传递数据(如主题、Context),避免层层传递参数。

1
2
3
4
5
6
7
8
val LocalUser = compositionLocalOf { User("Unknown") }

@Composable
fun App() {
CompositionLocalProvider(LocalUser provides User("Alice")) {
UserProfile() // 内部可以直接访问 LocalUser.current
}
}

9. 常见误区 (Common Pitfalls)

1. Padding 到底是内边距还是外边距?(Modifier 顺序)

这是初学者最容易困惑的地方。Compose 中没有专门的 margin 修饰符,只有 padding

padding 的作用取决于它在 Modifier 链中的位置。Modifier 是从左到右(或从外到内)顺序应用的,每一个 Modifier 都会包装(Wrap)前一个结果。

  • 作为外边距 (Margin): 如果 paddingbackgroundborder 之前应用,它表现得像外边距(因为背景色是在 padding 之后才绘制的,所以 padding 区域没有背景)。
  • 作为内边距 (Padding): 如果 paddingbackgroundborder 之后应用,它表现得像内边距(因为背景色已经绘制了,padding 只是在背景内部挤压内容)。

为什么这么设计?
这种设计基于单一职责原则链式调用。传统的 View 系统区分 margin 和 padding 是因为 View 是一个单一的对象,属性是固定的。而 Compose 的 Modifier 是一个有序的修饰链,通过组合简单的原子操作(padding, background)来构建复杂的 UI 效果。这消除了 “margin collapsing”(外边距折叠)等复杂性,也使得创建多层边框或复杂的间隔变得非常直观。

1
2
3
4
5
6
7
8
9
// 示例:同时实现外边距和内边距
Box(
modifier = Modifier
.padding(16.dp) // 1. 外边距 (Margin):在红色背景外部
.background(Color.Red) // 2. 绘制背景
.padding(8.dp) // 3. 内边距 (Padding):在红色背景内部,挤压内容
) {
Text("Content")
}

2. 在 Composable 中直接执行副作用

不要在 Composable 函数体中直接进行网络请求或数据库操作,这会导致每次重组都重复执行。

  • 正确做法: 使用 LaunchedEffectDisposableEffect

3. 重组循环 (Recomposition Loop)

在 Composable 中更新状态,而该状态的读取又触发了同一个 Composable 的重组。

1
2
3
4
5
6
7
// 错误示例
@Composable
fun BadComposable() {
var count by remember { mutableStateOf(0) }
count++ // 每次重组都会执行,导致无限循环
Text("$count")
}

4. 列表缺少 Key

LazyColumn 中,如果不指定 key,当列表项发生变化(如插入、删除)时,Compose 可能无法正确复用状态,导致性能下降或状态丢失。

1
items(items, key = { it.id }) { item -> ... }

10. 面试常见问题 (Interview Q&A)

Q1: 什么是重组 (Recomposition)?它是如何工作的?

A: 重组是 Compose 更新 UI 的机制。当 Composable 函数依赖的状态发生变化时,Compose 会重新执行该函数(及其子函数)以生成新的 UI 树。Compose 具有智能跳过机制(Smart Recomposition),只会重组受影响的部分,跳过参数未变化的组件。

Q2: rememberrememberSaveable 有什么区别?

A:

  • remember: 将对象保存在内存中。当 Composable 销毁(从组合中移除)时,状态丢失。配置更改(如旋转屏幕)时状态也会丢失。
  • rememberSaveable: 除了具备 remember 的功能外,还能在配置更改(Configuration Change)和进程重建后恢复状态(通过 Bundle 保存)。

Q3: LaunchedEffectDisposableEffect 的区别?

A:

  • LaunchedEffect: 用于启动协程。当进入组合时启动,离开组合时取消。适合一次性操作或挂起函数。
  • DisposableEffect: 用于需要清理的副作用(非协程)。当进入组合时执行 onActive,离开组合时执行 onDispose。适合注册/注销回调、监听器。

Q4: 为什么 Compose 性能比 View 高(或相当)?

A:

  • 扁平化布局: Compose 布局嵌套不会像 View 那样导致指数级的测量成本(Compose 规定布局只能测量子元素一次)。
  • 更少的代码: 减少了 XML 解析和 findViewById 的开销。
  • 智能重组: 只更新变化的部分。

Q5: 什么是 SideEffect?

A: SideEffect 是一个 Compose 副作用 API,用于在每次重组成功完成后执行代码。通常用于将 Compose 的状态同步给非 Compose 的系统(如更新系统状态栏颜色)。

11. 常用资源