Android Jetpack Compose 使用指南:从入门到实战
Android Jetpack Compose 使用指南:从入门到实战
Jetpack Compose 是 Android 现代 UI 工具包,使用声明式 Kotlin API 构建原生界面。本文系统梳理核心概念、常用组件、状态管理、副作用与最佳实践,助你快速上手并写出可维护的 Compose 代码。
目录
- 为什么选择 Compose
- 环境配置
- 核心概念
- 常用布局与组件
- 状态管理
- 副作用与 LaunchedEffect
- 主题与样式
- 列表与 Lazy 组件
- 导航 Navigation Compose
- 与 View 互操作
- 预览与调试
- 性能优化
- 最佳实践
- 常见坑与排错
为什么选择 Compose
| 维度 | 传统 View 体系 | Jetpack Compose |
|---|---|---|
| 编程范式 | 命令式(XML + 代码控制) | 声明式(UI = f(state)) |
| 复用单位 | 自定义 View / include | Composable 函数 |
| 状态同步 | 手动 findViewById、notifyDataSetChanged | 自动 recomposition |
| 代码量 | XML + Kotlin 双文件 | 单 Kotlin 文件 |
| 动画 | 复杂的 Animator API | animate*AsState 一行搞定 |
核心思想:UI 是状态的函数,状态变化时 Compose 自动重新执行受影响的 Composable,生成新的 UI 树并与旧树 diff 后应用最小变更。
环境配置
1. 最低要求
- Android Studio Arctic Fox(2020.3.1)及以上,推荐最新稳定版
- Kotlin 1.5.30 及以上
- compileSdk 31+(新版本推荐 34+)
- minSdk 21+(部分 API 需更高)
2. build.gradle.kts 配置
plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.compose") version "1.8.0" // 与 Kotlin 版本匹配
}
android {
compileSdk = 34
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
}
dependencies {
val composeBom = platform("androidx.compose:compose-bom:2024.09.00")
implementation(composeBom)
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material:material-icons-extended")
implementation("androidx.activity:activity-compose:1.9.2")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.6")
implementation("androidx.navigation:navigation-compose:2.8.1")
debugImplementation("androidx.compose.ui:ui-tooling")
}
提示:使用 BOM 可统一管理 Compose 库版本,无需逐个指定。
3. 入口 Activity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
核心概念
1. @Composable 注解
标记一个函数为可组合函数。规则:
- 必须在另一个 Composable 内调用
- 无返回值(返回 Unit)
- 函数名首字母大写(约定)
- 不能在协程、普通回调中直接调用(需用
rememberCoroutineScope等桥接)
2. Modifier
修饰符链式调用,控制布局、外观、交互。顺序敏感:
// 顺序不同,效果不同
Modifier.padding(8.dp).background(Color.Red) // padding 在外,背景不延伸到 padding 区
Modifier.background(Color.Red).padding(8.dp) // 背景延伸到 padding 区
常用 Modifier:
Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
.padding(16.dp)
.background(Color.LightGray, RoundedCornerShape(8.dp))
.clickable { /* onClick */ }
.clip(CircleShape)
.border(1.dp, Color.Black, CircleShape)
.size(48.dp)
3. Recomposition 机制
- Compose 跟踪每个 Composable 读取的状态
- 状态变化时,仅标记读取该状态的 Composable 为"过期"
- 下次帧重组时跳过未过期部分(智能跳过)
避免重组的要点:
- 传入 Composable 的参数类型应稳定(Stable / Immutable)
List<T>默认不稳定,用@Immutable标注或改用kotlinx.collections.immutable- 用
remember缓存计算结果
@Immutable
data class User(val id: String, val name: String, val avatar: String)
常用布局与组件
1. Column / Row / Box
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("第一行")
Text("第二行")
}
Row(
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text("左")
Text("右")
}
Box(
contentAlignment = Alignment.BottomEnd
) {
Image(painter = painterResource(R.drawable.bg), contentDescription = null)
Text("悬浮", modifier = Modifier.padding(8.dp))
}
2. Spacer
Column {
Text("A")
Spacer(modifier = Modifier.height(8.dp))
Text("B")
}
3. 常用 Material3 组件
// 按钮
Button(onClick = { /* */ }, modifier = Modifier.fillMaxWidth()) {
Text("确认")
}
OutlinedButton(onClick = { /* */ }) { Text("取消") }
TextButton(onClick = { /* */ }) { Text("跳过") }
// 文本输入
var text by remember { mutableStateOf("") }
OutlinedTextField(
value = text,
onValueChange = { text = it },
label = { Text("用户名") },
singleLine = true,
modifier = Modifier.fillMaxWidth()
)
// 卡片
Card(
modifier = Modifier.fillMaxWidth().padding(8.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text("标题", style = MaterialTheme.typography.titleLarge)
Text("内容描述")
}
}
// 开关
var checked by remember { mutableStateOf(false) }
Switch(checked = checked, onCheckedChange = { checked = it })
// 滑块
var sliderValue by remember { mutableStateOf(0.5f) }
Slider(value = sliderValue, onValueChange = { sliderValue = it })
4. Image / Icon
Image(
painter = painterResource(R.drawable.ic_logo),
contentDescription = "Logo", // 无障碍描述,装饰性图片用 null
modifier = Modifier.size(48.dp)
)
// 网络图片推荐使用 Coil
AsyncImage(
model = "https://example.com/avatar.png",
contentDescription = "头像",
modifier = Modifier.size(64.dp).clip(CircleShape)
)
Icon(
imageVector = Icons.Default.Home,
contentDescription = "主页",
tint = MaterialTheme.colorScheme.primary
)
状态管理
1. remember 与 mutableStateOf
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("点击了 $count 次")
}
}
remember:在重组间保持对象,配置变更(旋转屏幕)会丢失rememberSaveable:自动保存到 Bundle,配置变更后恢复
var name by rememberSaveable { mutableStateOf("") }
2. 状态提升(State Hoisting)
无状态 Composable + 状态由父级持有,便于复用与测试:
// 无状态版本(可复用、可测试)
@Composable
fun NameInput(name: String, onNameChange: (String) -> Unit) {
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("姓名") }
)
}
// 父级持有状态
@Composable
fun FormScreen() {
var name by remember { mutableStateOf("") }
NameInput(name = name, onNameChange = { name = it })
}
3. ViewModel + StateFlow
class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
fun updateUsername(value: String) {
_uiState.update { it.copy(username = value) }
}
}
data class LoginUiState(
val username: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginScreen(viewModel: LoginViewModel = viewModel()) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = uiState.username,
onValueChange = viewModel::updateUsername,
label = { Text("用户名") }
)
if (uiState.error != null) {
Text(uiState.error!!, color = MaterialTheme.colorScheme.error)
}
}
}
collectAsStateWithLifecycle在生命周期非活跃时自动停止收集,节省资源。
副作用与 LaunchedEffect
Composable 中不能直接启动协程或执行副作用,需使用副作用 API:
// 1. LaunchedEffect:进入组合时启动,key 变化时重启
@Composable
fun DataLoader(userId: String) {
LaunchedEffect(userId) {
val data = repository.fetchUser(userId) // suspend 函数
// 更新状态
}
}
// 2. rememberCoroutineScope:获取作用域用于事件触发
@Composable
fun SaveButton() {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch { repository.save() }
}) { Text("保存") }
}
// 3. DisposableEffect:进入/离开组合时执行清理
@Composable
fun LocationTracker() {
DisposableEffect(Unit) {
val listener = startLocationListener()
onDispose { listener.stop() }
}
}
// 4. derivedStateOf:从其他状态派生,避免多余重组
@Composable
fun ScrollExample() {
val scrollState = rememberScrollState()
val showButton by remember {
derivedStateOf { scrollState.value > 100 }
}
if (showButton) FloatingActionButton(onClick = {}) { Text("↑") }
}
| API | 触发时机 | 适用场景 |
|---|---|---|
LaunchedEffect |
进入组合 / key 变化 | 一次性 suspend 操作 |
rememberCoroutineScope |
组合内获取 | 事件回调中启动协程 |
DisposableEffect |
进入 / 离开组合 | 资源订阅与清理 |
SideEffect |
每次重组成功后 | 同步非 Compose 状态 |
derivedStateOf |
依赖变化时 | 派生状态、防抖 |
主题与样式
1. Material3 主题
@Composable
fun MyAppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColorAvailable() && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColorAvailable() && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
darkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colorScheme,
typography = AppTypography,
shapes = AppShapes,
content = content
)
}
2. 自定义 Typography
val AppTypography = Typography(
headlineLarge = TextStyle(
fontFamily = FontFamily.SansSerif,
fontWeight = FontWeight.Bold,
fontSize = 32.sp,
lineHeight = 40.sp
),
bodyLarge = TextStyle(
fontSize = 16.sp,
lineHeight = 24.sp
)
)
3. 使用主题值
Text(
text = "标题",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.titleLarge
)
列表与 Lazy 组件
1. LazyColumn / LazyRow
@Composable
fun UserList(users: List<User>) {
LazyColumn(
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(users, key = { it.id }) { user ->
UserRow(user)
}
item {
LoadMoreFooter()
}
}
}
@Composable
fun UserRow(user: User) {
Row(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
AsyncImage(model = user.avatar, contentDescription = null, modifier = Modifier.size(40.dp))
Spacer(Modifier.width(8.dp))
Text(user.name)
}
}
关键点:
- 提供
key:列表项有稳定 ID 时务必传入,避免重组错位与动画异常 - 避免在 item 中创建大对象:用
remember缓存 - 分页:搭配 Paging 3 +
collectAsLazyPagingItems()
2. LazyVerticalGrid
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(8.dp)
) {
items(photos) { photo -> PhotoCard(photo) }
}
导航 Navigation Compose
@Composable
fun AppNavHost() {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "home") {
composable("home") {
HomeScreen(onNavigateToDetail = { id ->
navController.navigate("detail/$id")
})
}
composable(
route = "detail/{userId}",
arguments = listOf(navArgument("userId") { type = NavType.StringType })
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId") ?: return@composable
DetailScreen(userId)
}
}
}
底部导航栏集成:
@Composable
fun BottomNavApp() {
val navController = rememberNavController()
Scaffold(
bottomBar = {
NavigationBar {
val items = listOf(
BottomNavItem.Home, BottomNavItem.Profile, BottomNavItem.Settings
)
items.forEach { item ->
NavigationBarItem(
selected = currentRoute == item.route,
onClick = { navController.navigate(item.route) },
icon = { Icon(item.icon, contentDescription = item.label) },
label = { Text(item.label) }
)
}
}
}
) { padding ->
AppNavHost(modifier = Modifier.padding(padding))
}
}
与 View 互操作
1. 在 Compose 中使用传统 View
@Composable
fun MapViewContainer() {
AndroidView(
factory = { context ->
com.google.android.gms.maps.MapView(context).apply {
// 初始化
}
},
update = { mapView ->
// 数据变化时更新
}
)
}
2. 在传统 View 中嵌入 Compose
<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("World") }
}
预览与调试
1. @Preview 注解
@Preview(name = "Light", showBackground = true)
@Preview(name = "Dark", uiMode = Configuration.UI_MODE_NIGHT_YES, showBackground = true)
@Preview(name = "Large Font", fontScale = 1.5f)
@Composable
fun GreetingPreview() {
MyAppTheme {
Greeting("Preview")
}
}
预览支持:暗黑模式、字体缩放、不同尺寸、Locale、多设备。在 Android Studio 中分屏即可实时查看。
2. Layout Inspector
Tools > Layout Inspector,启用后可查看 Compose 树、重组高亮(开启 “Show Recomposition Counts”)。
性能优化
1. 保持参数稳定
@Immutable
data class Article(val id: String, val title: String, val tags: List<String>)
2. 避免不必要的重组
- 用
derivedStateOf包装派生状态 - lambda 中读取的状态尽量下沉到具体 Composable
- 大列表用
key,并考虑LazyColumn的contentType提示
items(users, key = { it.id }, contentType = { "user" }) { user ->
UserRow(user)
}
3. 使用 Baseline Profile
发布包构建时启用 R8 + baseline profile,可显著提升首次启动时的 Compose 编译速度:
android {
baselineProfile {
mergeIntoMain = true
}
}
4. 稳定性配置
在 compose_compiler_config.conf 中标记第三方类为稳定:
stable_composable {
class com.example.ThirdPartyData
}
最佳实践
- 单一职责:每个 Composable 只做一件事,组合而非继承
- 状态提升:UI 与状态分离,无状态 Composable 易测试、易复用
- Modifier 参数:自定义 Composable 接受
modifier: Modifier = Modifier作为首个可选参数 - 避免在 Composable 中做副作用:用
LaunchedEffect/SideEffect - 预览优先:每个 UI 组件都写
@Preview,开发期即时验证 - 用
@Immutable/@Stable:标注数据类,帮助编译器优化重组 - 不要在 lambda 里捕获可变状态:用
rememberUpdatedState保持最新引用 - 主题化:颜色、字体、形状统一从
MaterialTheme取,避免硬编码
// 推荐:接受 modifier 参数
@Composable
fun Badge(
text: String,
modifier: Modifier = Modifier,
color: Color = MaterialTheme.colorScheme.primary
) {
Box(
modifier = modifier
.background(color, CircleShape)
.padding(horizontal = 8.dp, vertical = 4.dp)
) {
Text(text, color = Color.White, style = MaterialTheme.typography.labelSmall)
}
}
常见坑与排错
坑 1:状态丢失
现象:旋转屏幕后输入框清空。
原因:用了 remember 而非 rememberSaveable。
解决:
var text by rememberSaveable { mutableStateOf("") }
坑 2:无限重组 / StackOverflow
现象:在 Composable 中直接修改状态触发重组,形成循环。
解决:状态修改放在事件回调或 LaunchedEffect 中,不要在组合过程里写状态。
坑 3:列表动画错乱
原因:items() 未提供 key。
解决:传入稳定 key = { it.id }。
坑 4:Modifier 顺序导致布局异常
原因:padding 与 background、clickable 顺序影响点击区域与背景范围。
解决:理解 Modifier 是从左到右依次"包裹",先应用的在外层。
坑 5:collectAsState() vs collectAsStateWithLifecycle()
建议:始终用 collectAsStateWithLifecycle()(需 lifecycle-runtime-compose 依赖),在不可见时停止收集,省电省内存。
坑 6:Preview 不显示
检查清单:
debugImplementation("androidx.compose.ui:ui-tooling")是否添加- 预览函数无参数或参数有默认值
- 主题包裹正确
- 同步 Gradle 后 rebuild
结语
Jetpack Compose 用声明式思维重塑了 Android UI 开发:UI = f(state)。掌握状态管理、副作用、重组机制三大核心,再辅以 Modifier、Lazy 列表、导航、主题等组件,即可高效构建现代 Android 应用。
- 点赞
- 收藏
- 关注作者
评论(0)