Android Jetpack Compose 使用指南:从入门到实战

举报
yd_223002268 发表于 2026/08/23 22:17:21 2026/08/23
【摘要】 Android Jetpack Compose 使用指南:从入门到实战Jetpack Compose 是 Android 现代 UI 工具包,使用声明式 Kotlin API 构建原生界面。本文系统梳理核心概念、常用组件、状态管理、副作用与最佳实践,助你快速上手并写出可维护的 Compose 代码。 目录为什么选择 Compose环境配置核心概念常用布局与组件状态管理副作用与 Launch...

Android Jetpack Compose 使用指南:从入门到实战

Jetpack Compose 是 Android 现代 UI 工具包,使用声明式 Kotlin API 构建原生界面。本文系统梳理核心概念、常用组件、状态管理、副作用与最佳实践,助你快速上手并写出可维护的 Compose 代码。

目录

  1. 为什么选择 Compose
  2. 环境配置
  3. 核心概念
  4. 常用布局与组件
  5. 状态管理
  6. 副作用与 LaunchedEffect
  7. 主题与样式
  8. 列表与 Lazy 组件
  9. 导航 Navigation Compose
  10. 与 View 互操作
  11. 预览与调试
  12. 性能优化
  13. 最佳实践
  14. 常见坑与排错

为什么选择 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. remembermutableStateOf

@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,并考虑 LazyColumncontentType 提示
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
}

最佳实践

  1. 单一职责:每个 Composable 只做一件事,组合而非继承
  2. 状态提升:UI 与状态分离,无状态 Composable 易测试、易复用
  3. Modifier 参数:自定义 Composable 接受 modifier: Modifier = Modifier 作为首个可选参数
  4. 避免在 Composable 中做副作用:用 LaunchedEffect / SideEffect
  5. 预览优先:每个 UI 组件都写 @Preview,开发期即时验证
  6. @Immutable / @Stable:标注数据类,帮助编译器优化重组
  7. 不要在 lambda 里捕获可变状态:用 rememberUpdatedState 保持最新引用
  8. 主题化:颜色、字体、形状统一从 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 顺序导致布局异常

原因paddingbackgroundclickable 顺序影响点击区域与背景范围。
解决:理解 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 应用。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。