从 0 到 1:使用 Jetpack Compose 和智能自动化实现高效 Android UI 开发
现代 Android UI 开发正逐步从命令式 XML 向声明式 Compose 转变。Compose 凭借其简洁、高效、易测试的特点,能够让开发者更专注于界面和业务逻辑,而不必陷入大量模板化的代码。手把手带你构建一个完整的 Todo List 应用,并演示如何借助自动化工具大幅提升开发效率。
目录
- 为什么选用 Jetpack Compose?
- 环境与依赖准备
- Compose 基础入门
- 状态管理:驱动 UI 的心脏
- 布局与主题:打造统一的视觉风格
- 进阶:副作用、性能与动画
- 智能自动化:让重复工作“自动跑起来”
- 实战:构建 Todo List 应用
- 测试与 CI:质量保障
- 性能优化与最佳实践
- 总结与展望
- 参考文献
为什么选用 Jetpack Compose?
- 声明式:写 UI 就像写函数,传入数据,输出界面,避免了 XML +
findViewById
的繁琐。 - 可组合:所有 UI 元素都是
@Composable
函数,易于拆分、复用和测试。 - 内建状态管理:结合 Kotlin 特性,
remember
、mutableStateOf
等 API,让状态驱动界面更新,重组(recomposition)高效且可预测。 - 生态完善:官方提供 Material3 组件、Accompanist 辅助库、与导航、生命周期等 Jetpack 库深度集成。
环境与依赖准备
-
Android Studio:推荐使用 Arctic Fox (2020.3.1) 或更高版本。
-
Kotlin:版本 1.8.0+。
-
Gradle 配置:
// 项目级 build.gradle buildscript {ext {compose_version = '1.5.0'kotlin_version = '1.8.0'}dependencies {classpath "com.android.tools.build:gradle:8.0.0"classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"} }// 模块级 build.gradle plugins {id 'com.android.application'id 'org.jetbrains.kotlin.android'id "kotlin-kapt"id "io.github.raamcosta.compose-destinations" version "1.8.5" }android {compileSdk 34defaultConfig {applicationId "com.example.todo"minSdk 21targetSdk 34}buildFeatures {compose true}composeOptions {kotlinCompilerExtensionVersion compose_version}kotlinOptions {jvmTarget = "1.8"} }dependencies {implementation "androidx.core:core-ktx:1.10.1"implementation "androidx.compose.ui:ui:$compose_version"implementation "androidx.compose.material3:material3:1.1.0"implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.6.0"implementation "androidx.activity:activity-compose:1.7.0"implementation "io.github.raamcosta.compose-destinations:animations-core:1.8.5"kapt "io.github.raamcosta.compose-destinations:ksp:1.8.5" }
Tips: 在 Android Studio 设置里(Preferences → Experimental)确保已开启 Compose 支持。
Compose 基础入门
认识 @Composable
@Composable
fun Greeting(name: String) {Text(text = "Hello, $name!")
}
- 声明式:直接描述“界面应该是什么样子”,数据变化时 Compose 自动重组。
- 无 UI 对象:没有
View
、Activity
等概念,只有函数调用嵌套。
布局容器
- Column:垂直排列
- Row:水平排列
- Box:堆叠布局
@Composable
fun ProfileCard() {Row(modifier = Modifier.padding(16.dp)) {Image(/*…*/)Column(modifier = Modifier.padding(start = 8.dp)) {Text("Alice")Text("Android Developer")}}
}
修饰符(Modifier
)
- 链式调用:
.fillMaxWidth().padding(8.dp).background(Color.Gray)
- 常用API:
size()
,padding()
,background()
,clickable()
……
状态管理:驱动 UI 的心脏
mutableStateOf
与 remember
@Composable
fun Counter() {var count by remember { mutableStateOf(0) }Button(onClick = { count++ }) {Text("Clicked $count times")}
}
remember
:组件重组时保留状态mutableStateOf
:包装可观察状态,值变时自动触发重组
StateFlow
结合 Compose
在 ViewModel 中:
class TodoViewModel : ViewModel() {private val _todos = MutableStateFlow<List<Todo>>(emptyList())val todos: StateFlow<List<Todo>> = _todosfun load() { /*…*/ }
}
在 Composable:
@Composable
fun TodoList(viewModel: TodoViewModel = hiltViewModel()) {val list by viewModel.todos.collectAsState()LazyColumn { items(list) { TodoItem(it) } }
}
布局与主题:打造统一的视觉风格
Material3 主题
@Composable
fun MyTheme(content: @Composable ()->Unit) {MaterialTheme(colorScheme = lightColorScheme(primary = Color(0xFF6200EE),secondary = Color(0xFF03DAC6)),typography = Typography(/*…*/),content = content)
}
自定义组件
@Composable
fun MyButton(text: String, onClick: ()->Unit) {Button(onClick = onClick,shape = RoundedCornerShape(8.dp),modifier = Modifier.fillMaxWidth()) {Text(text.uppercase(), fontWeight = FontWeight.Bold)}
}
进阶:副作用、性能与动画
副作用 API
LaunchedEffect(key)
:基于键启动协程SideEffect
:在每次成功重组后执行DisposableEffect
:绑定 / 解绑资源
性能优化
remember
缓存计算结果derivedStateOf
:避免不必要重组- 拆分小组件:让重组局部化
简单动画
@Composable
fun PulsingDot() {val scale by animateFloatAsState(targetValue = if (expanded) 1.5f else 1f,animationSpec = tween(1000, easing = LinearEasing))Box(modifier = Modifier.size(50.dp).graphicsLayer { scaleX = scale; scaleY = scale }.background(Color.Red, CircleShape).clickable { expanded = !expanded })
}
智能自动化:让重复工作“自动跑起来”
-
Live Templates
-
在 IDE 中预设 Compose 代码片段,如
@cmp
:@Composable fun $NAME$() {$END$ }
-
极大减少样板代码输入。
-
-
Compose Destinations
- 注解一行:
@Destination
- 编译时自动生成导航:无须手写 NavHost、NavController。
- 注解一行:
-
GitHub Copilot / AI 助手
- 在代码中写注释
// TODO: fetch from repo
- Copilot 自动补全网络请求、JSON 解析等模板。
- 在代码中写注释
-
Screenshot Testing(Paparazzi)
- 快速对比 UI 回归,自动化生成、校对截图。
实战构建 Todo List 应用
项目结构
com.example.todo
├── MainActivity.kt
├── navigation/ // Destinations 生成
├── ui/
│ ├── TodoListScreen.kt
│ └── EditTodoScreen.kt
└── viewmodel/└── TodoViewModel.kt
关键代码
TodoListScreen.kt
@Destination(start = true)
@Composable
fun TodoListScreen(navigator: DestinationsNavigator,viewModel: TodoViewModel = hiltViewModel()
) {val todos by viewModel.todos.collectAsState()Scaffold(topBar = { TopAppBar(title = { Text("我的待办") }) },floatingActionButton = {FloatingActionButton(onClick = { navigator.navigate(EditTodoScreenDestination(null)) }) {Icon(Icons.Default.Add, contentDescription = "添加")}}) { padding ->LazyColumn(modifier = Modifier.padding(padding)) {items(todos) { todo ->Card(modifier = Modifier.fillMaxWidth().padding(8.dp).clickable { navigator.navigate(EditTodoScreenDestination(todo.id)) }) {Text(todo.title, modifier = Modifier.padding(16.dp))}}}}
}
TodoViewModel.kt
@HiltViewModel
class TodoViewModel @Inject constructor(private val repo: TodoRepository
): ViewModel() {private val _todos = MutableStateFlow<List<Todo>>(emptyList())val todos: StateFlow<List<Todo>> = _todosinit { loadTodos() }fun loadTodos() = viewModelScope.launch {_todos.value = repo.getAllTodos()}fun save(todo: Todo) = viewModelScope.launch {repo.save(todo)loadTodos()}
}
测试与 CI:质量保障
-
Compose UI Test
@get:Rule val rule = createComposeRule() @Test fun addButtonNavigates() {rule.setContent { TodoListScreen(/*…*/) }rule.onNodeWithContentDescription("添加").performClick()rule.onNodeWithText("编辑待办").assertExists() }
-
Paparazzi 截图测试
@Test fun todoListSnapshot() = paparazzi.snapshot {TodoListScreen(/*…*/) }
-
GitHub Actions
- 运行
./gradlew testDebugUnitTest
和./gradlew testDebugAndroidTest
- 对比 Paparazzi 输出,回归提醒。
- 运行
性能优化与最佳实践
- 局部重组:把可变状态限制在最小组件里。
- 使用
derivedStateOf
:复杂计算结果缓存。 - 避免在组合函数中做 I/O:利用
LaunchedEffect
。 - 自定义 Layout:对于特殊需求可编写自己的布局算法。