Jetpack Compose 快速上手

Jetpack Compose 是 Android 官方的现代 UI 工具包。它用 Kotlin 函数描述界面:数据变化时,Compose 会重新执行受影响的 UI 代码,把界面更新到新的状态。

本文按照"原理 → 特点 → 代码 → 使用场景"的节奏,带你完成一个最小任务清单,并串起 Compose 开发最常用的知识点。示例默认使用 Kotlin、Material 3、ViewModel 与 Navigation Compose

版本提示:Compose 更新较快。新项目优先使用 Android Studio 的 Compose 模板,让 IDE 生成彼此兼容的 Kotlin、Compose Compiler、Compose BOM 与 Android Gradle Plugin 版本。本文刻意不写死版本号。

总览

知识点 常用 API 核心作用 典型场景
可组合函数 @Composable 用函数描述 UI 页面、组件、列表项
修饰符 Modifier 控制尺寸、间距、点击、绘制 绝大多数 UI 元素
基础布局 RowColumnBox 横向、纵向、层叠排列 常规页面布局
状态 rememberrememberSaveable 保存 UI 状态并触发重组 输入框、开关、选中项
列表 LazyColumnLazyRow 按需组合可见项 消息流、商品列表
Material 3 MaterialThemeScaffold 统一颜色、字体和页面骨架 App 主题、TopBar、FAB
状态提升 value + callback 保持组件无状态、便于复用 表单、业务组件
ViewModel StateFlow 承载业务状态并跨配置变化 页面状态、异步数据
副作用 LaunchedEffectDisposableEffect 安全执行非 UI 工作 请求、监听、清理资源
导航 NavHostNavController 管理页面与返回栈 多页面 App
View 互操作 AndroidViewComposeView 渐进迁移旧页面 地图、WebView、遗留 XML

1. Compose 是什么

原理

传统 View 通常按步骤修改控件:找到 TextView,再调用 setText()。Compose 更关心"当前状态下界面应该长什么样"。

kotlin 复制代码
@Composable
fun Greeting(name: String) {
    Text(text = "你好,$name")
}

name 变化后,Compose 会重新执行依赖它的代码。这个过程叫 重组(Recomposition)。重组不是重建整个 Activity,也不意味着整个界面都会重绘;Compose 会尽量跳过输入未变化的部分。

特点

  • UI 与状态关系直接,减少手动同步控件的代码。
  • 组件就是 Kotlin 函数,容易拆分、预览和测试。
  • 与 Kotlin 协程、Flow、ViewModel、Lifecycle 配合自然。
  • 可以和原有 View/XML 混合使用,不必一次性重写项目。

适合的使用场景

  • 新建 Android 页面或新项目。
  • 需要快速迭代的业务 UI。
  • 多状态页面、动态主题、动画和响应式布局。
  • 在旧项目中逐页迁移 XML。

2. 创建第一个 Compose 工程

推荐方式

在 Android Studio 中选择支持 Compose 的空 Activity 模板。模板会自动完成以下配置:

  • 启用 Compose 构建功能;
  • 配置 Compose Compiler;
  • 引入 Activity Compose 与 Material 3;
  • 创建继承 ComponentActivity 的入口 Activity。

手动配置时,模块级 build.gradle.kts 的核心结构如下。版本请交给 Version Catalog 或 Compose BOM 统一管理。

kotlin 复制代码
android {
    buildFeatures {
        compose = true
    }
}

dependencies {
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.activity.compose)
    implementation(libs.androidx.compose.ui)
    implementation(libs.androidx.compose.ui.tooling.preview)
    implementation(libs.androidx.compose.material3)

    debugImplementation(libs.androidx.compose.ui.tooling)
}

Activity 入口

kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContent {
            QuickStartTheme {
                Surface(modifier = Modifier.fillMaxSize()) {
                    Greeting(name = "Compose")
                }
            }
        }
    }
}

setContent {} 是 View 世界进入 Compose 世界的入口。花括号里的内容就是这棵 Compose UI 树的根节点。


3. 可组合函数 Composable

原理

@Composable 标记的函数可以调用其他可组合函数。它通常接收数据和事件回调,不直接持有业务对象。

kotlin 复制代码
@Composable
fun UserCard(
    name: String,
    online: Boolean,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Card(
        onClick = onClick,
        modifier = modifier,
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = name, style = MaterialTheme.typography.titleMedium)
            Text(text = if (online) "在线" else "离线")
        }
    }
}

特点

  • 函数名通常使用大驼峰,例如 UserCard
  • modifier 建议作为可选参数暴露给调用者。
  • 参数应尽量描述 UI 所需的最小数据。
  • 事件通过 lambda 向上通知,如 onClickonValueChange

Preview 预览

kotlin 复制代码
@Preview(showBackground = true)
@Composable
private fun UserCardPreview() {
    QuickStartTheme {
        UserCard(
            name = "Ada",
            online = true,
            onClick = {},
        )
    }
}

Preview 适合检查独立组件。预览里尽量传入静态假数据,不要直接启动网络请求或依赖真实数据库。


4. Modifier:Compose 布局的通用语言

原理

Modifier 是不可变的修饰符链,用来改变元素的布局、绘制、交互与语义。链式调用的顺序会影响结果。

kotlin 复制代码
Text(
    text = "开始学习",
    modifier = Modifier
        .padding(16.dp)
        .background(
            color = MaterialTheme.colorScheme.primaryContainer,
            shape = RoundedCornerShape(12.dp),
        )
        .clickable { /* 点击事件 */ }
        .padding(horizontal = 20.dp, vertical = 12.dp),
)

这里的第一次 padding 是组件外间距,第二次 padding 是背景内部的留白。

常用 Modifier

API 作用
fillMaxWidth() 占满可用宽度
size() / width() / height() 设置尺寸
padding() 添加布局留白
background() 绘制背景
clip() 裁剪形状
clickable() 增加点击行为与语义
weight() 按比例分配 Row/Column 剩余空间
align() 在父布局作用域中对齐
semantics {} 补充无障碍与测试语义

使用建议

  • 组件内部使用 modifier 参数时,通常只应用一次,并放在最外层节点。
  • 不要把调用方传入的 modifier 擅自替换为 Modifier
  • 视觉间距、点击范围异常时,先检查修饰符顺序。

5. Row、Column 与 Box

原理

  • Row:子项从左到右排列;
  • Column:子项从上到下排列;
  • Box:子项可以层叠;
  • Spacer:创建明确的空白;
  • ArrangementAlignment:控制主轴分布和交叉轴对齐。
kotlin 复制代码
@Composable
fun ProfileHeader(
    name: String,
    subtitle: String,
    modifier: Modifier = Modifier,
) {
    Row(
        modifier = modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Box(
            modifier = Modifier
                .size(48.dp)
                .clip(CircleShape)
                .background(MaterialTheme.colorScheme.primaryContainer),
            contentAlignment = Alignment.Center,
        ) {
            Text(text = name.take(1))
        }

        Spacer(modifier = Modifier.width(12.dp))

        Column(modifier = Modifier.weight(1f)) {
            Text(text = name, style = MaterialTheme.typography.titleMedium)
            Text(text = subtitle, style = MaterialTheme.typography.bodyMedium)
        }

        IconButton(onClick = { }) {
            Icon(Icons.Default.MoreVert, contentDescription = "更多")
        }
    }
}

使用场景

  • Row:工具栏、头像信息、横向按钮组。
  • Column:表单、文章、设置页。
  • Box:角标、遮罩、图片上的文字、加载层。

避坑:不要用嵌套很多层的 Row/Column 模拟复杂滚动列表。大量数据请使用 Lazy 布局。


6. 状态与重组

原理

Compose 观察 State<T>。当它的 value 变化时,读取过该值的可组合函数会进入重组。

kotlin 复制代码
@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }

    Row(verticalAlignment = Alignment.CenterVertically) {
        Text(text = "点击次数:$count")
        Spacer(modifier = Modifier.width(12.dp))
        Button(onClick = { count++ }) {
            Text("+1")
        }
    }
}

remember 能跨重组保存值,但 Activity 重建后可能丢失。简单且可保存的 UI 状态可以用 rememberSaveable

kotlin 复制代码
@Composable
fun NameInput() {
    var name by rememberSaveable { mutableStateOf("") }

    OutlinedTextField(
        value = name,
        onValueChange = { name = it },
        label = { Text("昵称") },
        singleLine = true,
    )
}

三种常见状态位置

状态类型 推荐位置 示例
仅影响当前小组件 remember 展开/收起、临时动画状态
需要跨 Activity 重建 rememberSaveable 输入文本、Tab 下标
业务状态或跨组件共享 ViewModel 页面数据、加载结果、用户操作

常见错误

kotlin 复制代码
// 错误:每次重组都会重新赋值,且普通变量变化不会通知 Compose。
var count = 0

UI 需要响应数据变化时,应把可观察状态放在稳定的持有者中。


7. 状态提升:让组件更容易复用

原理

状态提升是把组件内部的状态拆成两个参数:

  • value:当前状态;
  • onValueChange:请求修改状态的事件。
kotlin 复制代码
@Composable
fun SearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    modifier: Modifier = Modifier,
) {
    OutlinedTextField(
        value = query,
        onValueChange = onQueryChange,
        modifier = modifier.fillMaxWidth(),
        placeholder = { Text("搜索任务") },
        leadingIcon = {
            Icon(Icons.Default.Search, contentDescription = null)
        },
        singleLine = true,
    )
}

调用方负责真正保存数据:

kotlin 复制代码
@Composable
fun SearchScreen() {
    var query by rememberSaveable { mutableStateOf("") }
    SearchBar(
        query = query,
        onQueryChange = { query = it },
    )
}

特点

  • 子组件变成无状态组件,预览和测试更简单。
  • 父组件拥有唯一数据源,避免多个副本不同步。
  • 组件只暴露事件,不需要知道 ViewModel 或导航对象。

8. LazyColumn:高效显示列表

原理

Column.verticalScroll() 会组合所有子项;LazyColumn 只组合当前可见区域附近的项目,更适合大量或动态数据。

kotlin 复制代码
data class Task(
    val id: Long,
    val title: String,
    val completed: Boolean,
)

@Composable
fun TaskList(
    tasks: List<Task>,
    onToggle: (Long) -> Unit,
    modifier: Modifier = Modifier,
) {
    LazyColumn(
        modifier = modifier.fillMaxSize(),
        contentPadding = PaddingValues(16.dp),
        verticalArrangement = Arrangement.spacedBy(10.dp),
    ) {
        items(
            items = tasks,
            key = { task -> task.id },
        ) { task ->
            TaskItem(
                task = task,
                onToggle = { onToggle(task.id) },
                modifier = Modifier.animateItem(),
            )
        }
    }
}

@Composable
private fun TaskItem(
    task: Task,
    onToggle: () -> Unit,
    modifier: Modifier = Modifier,
) {
    Card(modifier = modifier.fillMaxWidth()) {
        Row(
            modifier = Modifier.padding(16.dp),
            verticalAlignment = Alignment.CenterVertically,
        ) {
            Checkbox(
                checked = task.completed,
                onCheckedChange = { onToggle() },
            )
            Spacer(modifier = Modifier.width(12.dp))
            Text(
                text = task.title,
                modifier = Modifier.weight(1f),
                textDecoration = if (task.completed) {
                    TextDecoration.LineThrough
                } else {
                    TextDecoration.None
                },
            )
        }
    }
}

特点

  • items() 用于列表数据,item {} 用于单个固定内容。
  • 为可变列表提供稳定且唯一的 key,有利于保留项目状态和动画。
  • 不要用数组下标作为会插入、删除或排序的数据的长期身份。
  • LazyRow 用法类似,方向改为横向。

9. Material 3 与 Scaffold

原理

MaterialTheme 提供颜色、字体和形状;Scaffold 提供 TopBar、BottomBar、FAB、Snackbar 与内容区域的页面骨架。

kotlin 复制代码
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TaskScreen(
    tasks: List<Task>,
    onAdd: () -> Unit,
    onToggle: (Long) -> Unit,
) {
    Scaffold(
        topBar = {
            TopAppBar(title = { Text("任务清单") })
        },
        floatingActionButton = {
            FloatingActionButton(onClick = onAdd) {
                Icon(Icons.Default.Add, contentDescription = "添加任务")
            }
        },
    ) { innerPadding ->
        TaskList(
            tasks = tasks,
            onToggle = onToggle,
            modifier = Modifier.padding(innerPadding),
        )
    }
}

主题

kotlin 复制代码
private val LightColors = lightColorScheme(
    primary = Color(0xFF65558F),
    secondary = Color(0xFF006A60),
)

@Composable
fun QuickStartTheme(content: @Composable () -> Unit) {
    MaterialTheme(
        colorScheme = LightColors,
        typography = Typography(),
        content = content,
    )
}

使用建议

  • 业务组件优先读取 MaterialTheme.colorScheme,避免散落硬编码颜色。
  • ScaffoldinnerPadding 必须传给内容,否则内容可能被 TopBar 遮挡。
  • 图标按钮需要有意义的 contentDescription;纯装饰图标可传 null

10. ViewModel + StateFlow:承载业务状态

原理

ViewModel 负责业务状态与事件处理,Composable 负责展示状态与上报用户操作。数据通常单向流动:

text 复制代码
StateFlow<UiState>  ──>  UI
        ViewModel  <──  UI Event

ViewModel

kotlin 复制代码
data class TaskUiState(
    val tasks: List<Task> = emptyList(),
    val isLoading: Boolean = false,
    val message: String? = null,
)

class TaskViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(
        TaskUiState(
            tasks = listOf(
                Task(1, "认识 Composable", true),
                Task(2, "掌握状态提升", false),
                Task(3, "完成任务列表", false),
            )
        )
    )

    val uiState: StateFlow<TaskUiState> = _uiState.asStateFlow()

    fun toggleTask(id: Long) {
        _uiState.update { state ->
            state.copy(
                tasks = state.tasks.map { task ->
                    if (task.id == id) {
                        task.copy(completed = !task.completed)
                    } else {
                        task
                    }
                }
            )
        }
    }
}

在 Compose 中收集状态

kotlin 复制代码
@Composable
fun TaskRoute(
    viewModel: TaskViewModel = viewModel(),
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    TaskScreen(
        tasks = uiState.tasks,
        onAdd = { /* 打开新增页面 */ },
        onToggle = viewModel::toggleTask,
    )
}

特点

  • collectAsStateWithLifecycle() 会结合生命周期收集 Flow。
  • 对外暴露只读 StateFlow,内部保留可变实现。
  • UiState 使用不可变数据,更新时创建新值。
  • 不要把 ContextActivityNavController 长期保存到 ViewModel。

11. 副作用 Side Effects

原理

Composable 可能多次执行,因此网络请求、启动协程、注册监听等操作不能直接写在函数体里。Compose 提供副作用 API,让这些工作和 UI 生命周期对齐。

LaunchedEffect

当 key 进入组合或发生变化时启动协程;离开组合时协程自动取消。

kotlin 复制代码
@Composable
fun UserScreen(
    userId: String,
    loadUser: suspend (String) -> Unit,
) {
    LaunchedEffect(userId) {
        loadUser(userId)
    }
}

DisposableEffect

适合需要成对注册和清理的资源。

kotlin 复制代码
@Composable
fun LifecycleLogger(owner: LifecycleOwner = LocalLifecycleOwner.current) {
    DisposableEffect(owner) {
        val observer = LifecycleEventObserver { _, event ->
            Log.d("Lifecycle", event.name)
        }
        owner.lifecycle.addObserver(observer)

        onDispose {
            owner.lifecycle.removeObserver(observer)
        }
    }
}

rememberCoroutineScope

适合由点击事件触发、且生命周期绑定当前组合的协程。

kotlin 复制代码
val scope = rememberCoroutineScope()

Button(
    onClick = {
        scope.launch {
            snackbarHostState.showSnackbar("保存成功")
        }
    }
) {
    Text("保存")
}

选择建议

需求 API
随 key 变化执行挂起任务 LaunchedEffect(key)
注册并清理监听器 DisposableEffect(key)
点击后启动协程 rememberCoroutineScope()
把 Compose 状态暴露给非 Compose 对象 SideEffect
把多个状态计算成派生状态 derivedStateOf

依赖

kotlin 复制代码
dependencies {
    implementation(libs.androidx.navigation.compose)
}

最小导航图

kotlin 复制代码
@Composable
fun AppNavHost() {
    val navController = rememberNavController()

    NavHost(
        navController = navController,
        startDestination = "tasks",
    ) {
        composable("tasks") {
            TaskListRoute(
                onTaskClick = { taskId ->
                    navController.navigate("task/$taskId")
                }
            )
        }

        composable(
            route = "task/{taskId}",
            arguments = listOf(
                navArgument("taskId") { type = NavType.LongType }
            ),
        ) { backStackEntry ->
            val taskId = backStackEntry.arguments?.getLong("taskId") ?: return@composable
            TaskDetailScreen(
                taskId = taskId,
                onBack = navController::navigateUp,
            )
        }
    }
}

这里的 TaskListRoute 表示列表页入口;它需要把 onTaskClick 继续传给具体列表项。这样页面只发出"打开任务"的事件,不直接持有导航对象。

特点

  • 页面接收 onBackonTaskClick 等回调,不必直接依赖 NavController
  • 导航参数尽量只传 ID,再从数据层加载完整对象。
  • 生产项目可采用 Navigation Compose 当前稳定版支持的类型安全路由,减少字符串拼写错误。
  • navigateUp() 适合处理返回行为。

13. 与传统 View/XML 混合使用

在 Compose 中使用 View

kotlin 复制代码
@Composable
fun LegacyWebView(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                webViewClient = WebViewClient()
            }
        },
        update = { webView ->
            if (webView.url != url) {
                webView.loadUrl(url)
            }
        },
        modifier = Modifier.fillMaxSize(),
    )
}

在 XML 中嵌入 Compose

xml 复制代码
<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
kotlin 复制代码
findViewById<ComposeView>(R.id.compose_view).setContent {
    QuickStartTheme {
        UserCard(
            name = "Compose",
            online = true,
            onClick = {},
        )
    }
}

使用场景

  • 地图、播放器、WebView 等已有成熟 View 组件。
  • 大型旧项目逐页迁移。
  • 先用 Compose 开发新功能,再逐步替换历史 XML。

14. 性能与调试

核心原则

Compose 性能优化的重点不是"避免所有重组",而是让状态范围合理、参数稳定,并避免在重组时重复做昂贵工作。

kotlin 复制代码
@Composable
fun FilteredTaskList(
    tasks: List<Task>,
    query: String,
) {
    val filteredTasks by remember(tasks, query) {
        derivedStateOf {
            tasks.filter { it.title.contains(query, ignoreCase = true) }
        }
    }

    TaskList(
        tasks = filteredTasks,
        onToggle = {},
    )
}

常见建议

  • 列表项使用稳定 key
  • 昂贵计算使用 remember,并提供正确的 key。
  • 不要在 Composable 函数体里直接请求网络、读文件或创建大型对象。
  • 先用 Layout Inspector、系统跟踪和基准测试定位问题,再优化。
  • 对高频变化值,可使用 derivedStateOf 降低不必要的 UI 更新。
  • 动画或滚动卡顿时,检查是否在每一帧分配大量对象。

常见误区

误区 正确理解
重组等于整个页面重绘 Compose 会定位读取了变化状态的部分,并尝试跳过其他节点
remember 可以永久保存数据 它主要跨重组保存;业务数据应放入 ViewModel/数据层
所有状态都放 ViewModel 纯 UI 临时状态放在最接近使用处更清晰
Column 可以替代所有列表 大数据列表应使用 Lazy 布局
Modifier 顺序无所谓 顺序会改变尺寸、点击范围和绘制结果

15. 一个可运行的最小任务清单

下面把状态、列表、Material 3 与状态提升组合到一个文件中。把它放入 Compose 模板项目即可运行。

kotlin 复制代码
data class SimpleTask(
    val id: Int,
    val title: String,
    val done: Boolean = false,
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TodoApp() {
    var input by rememberSaveable { mutableStateOf("") }
    var nextId by rememberSaveable { mutableIntStateOf(3) }
    var tasks by remember {
        mutableStateOf(
            listOf(
                SimpleTask(1, "创建 Compose 项目", true),
                SimpleTask(2, "完成第一个列表"),
            )
        )
    }

    Scaffold(
        topBar = {
            TopAppBar(title = { Text("Compose Todo") })
        }
    ) { innerPadding ->
        Column(
            modifier = Modifier
                .fillMaxSize()
                .padding(innerPadding)
                .padding(16.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp),
        ) {
            Row(
                verticalAlignment = Alignment.CenterVertically,
                horizontalArrangement = Arrangement.spacedBy(8.dp),
            ) {
                OutlinedTextField(
                    value = input,
                    onValueChange = { input = it },
                    modifier = Modifier.weight(1f),
                    label = { Text("新任务") },
                    singleLine = true,
                )

                Button(
                    enabled = input.isNotBlank(),
                    onClick = {
                        tasks = tasks + SimpleTask(nextId++, input.trim())
                        input = ""
                    },
                ) {
                    Text("添加")
                }
            }

            LazyColumn(
                verticalArrangement = Arrangement.spacedBy(8.dp),
            ) {
                items(tasks, key = { it.id }) { task ->
                    Card(modifier = Modifier.fillMaxWidth()) {
                        Row(
                            modifier = Modifier.padding(12.dp),
                            verticalAlignment = Alignment.CenterVertically,
                        ) {
                            Checkbox(
                                checked = task.done,
                                onCheckedChange = { checked ->
                                    tasks = tasks.map {
                                        if (it.id == task.id) {
                                            it.copy(done = checked)
                                        } else {
                                            it
                                        }
                                    }
                                },
                            )
                            Text(
                                text = task.title,
                                modifier = Modifier.weight(1f),
                            )
                            IconButton(
                                onClick = {
                                    tasks = tasks.filterNot { it.id == task.id }
                                }
                            ) {
                                Icon(
                                    imageVector = Icons.Default.Delete,
                                    contentDescription = "删除 ${task.title}",
                                )
                            }
                        }
                    }
                }
            }
        }
    }
}

这个示例把状态保存在页面内,适合学习。接入数据库或网络后,应把任务数据与增删改逻辑迁移到 ViewModel 和 Repository。


常见选择建议

需求 推荐方案
少量、固定、无需滚动的内容 Column / Row
长列表或不确定数量的数据 LazyColumn / LazyRow
仅当前组件使用的临时状态 remember
需要跨 Activity 重建的简单 UI 状态 rememberSaveable
页面级业务状态 ViewModel + StateFlow
状态由多个值计算得出 derivedStateOf 或在 ViewModel 中计算
进入页面后加载数据 ViewModel 初始化,或谨慎使用 LaunchedEffect
注册系统监听并在离开时清理 DisposableEffect
新项目统一视觉系统 Material 3 主题
旧项目渐进迁移 ComposeView + AndroidView

面试回答模板

如果被问"Jetpack Compose 的核心是什么",可以这样回答:

Jetpack Compose 是 Android 的声明式 UI 工具包。开发者用 Composable 函数描述状态对应的界面,状态变化后 Compose 通过重组更新读取该状态的 UI。项目中通常通过状态提升保持组件无状态,再用 ViewModel 和 StateFlow 管理页面级业务状态。副作用使用 LaunchedEffect、DisposableEffect 等 API 与组合生命周期对齐,列表使用 Lazy 布局并提供稳定 key。

如果继续追问重组,可补充三点:

  1. 重组可能频繁发生,因此 Composable 应尽量无副作用、执行快速。
  2. 重组不等于 Activity 重建,也不等于整棵 UI 树重绘。
  3. 状态读取的位置决定了受影响的重组范围,应把状态放在合理层级。

学习建议

建议按以下顺序练习:

  1. TextButtonRowColumn 还原一个静态页面。
  2. rememberSaveable 完成计数器和输入框。
  3. 用状态提升拆出无状态组件。
  4. LazyColumn 完成可增删改的任务列表。
  5. 把页面状态迁移到 ViewModel + StateFlow
  6. 增加详情页,练习 Navigation Compose。
  7. 加入加载、空数据、错误三种页面状态。
  8. 最后再学习动画、自定义 Layout、Canvas 与性能基准测试。

只记住一句话:UI 是状态的函数,事件负责请求改变状态。 抓住这条主线,Compose 的大部分 API 都会变得容易理解。


参考资料

相关推荐
y小花1 小时前
安卓HAL接口技术之HIDL&AIDL
android
YF02111 小时前
Android 系统自带机制的卸载更新都哪些实现方案
android
事圆则缓1 小时前
MVC、MVP、MVVM、MVI 的区别与实现原理
android·kotlin·mvc
事圆则缓2 小时前
Android组件化指南
android·kotlin
binbin_523 小时前
HarmonyOS 应用功耗优化实战:定位耗电、收口任务与验证回归
android·回归·harmonyos
AFinalStone3 小时前
Android 7系统无障碍服务(七)手势分发与 KeyEvent 处理
android·无障碍服务
事圆则缓4 小时前
Jetpack Compose Effect 完全指南
android·kotlin
平头哥技术团队13 小时前
Day 10 | 工欲善其事:VS Code 配置与项目归档
android·开发语言·前端·javascript·html·交互
冬木家居14 小时前
40㎡客厅变形记,小家住出大自由[特殊字符]
android·经验分享·笔记·智能家居·微信公众平台