Jetpack Compose UI - 命令式到声明式

1.1 从命令式到声明式

传统 Android 开发:你写代码告诉系统一步一步做什么。

<!-- activity_main.xml -->
<TextView android:id="@+id/tvGreeting" android:text="Hello" />
// MainActivity.java
TextView tv = findViewById(R.id.tvGreeting);
tv.setText("Hello, World!");

Compose 开发:你描述 UI 在当前状态下应该长什么样,系统自动计算差异并更新。

@Composable
fun Greeting(name: String) {
    Text("Hello, $name!")
}
  • 这里的 Greeting 是一个可组合函数(Composable function),它接收状态 name,返回 UI 描述(Text)。
  • name 变化时,Compose 会重组(recompose)这个函数,生成新的 UI 描述,并与旧描述做 diff,只更新必要的部分。

1.2 核心概念:重组(Recomposition)

@Composable
fun Counter() {
    var count by remember { mutableIntStateOf(0) }  // 可观察状态

    Column {
        Text("You clicked $count times")
        Button(onClick = { count++ }) {
            Text("Click me")
        }
    }
}
  • mutableIntStateOf(0) 创建一个可观察的整数状态。
  • remember 保证在重组过程中这个状态不会被重置(只有当 Composable 从组合树中移除时才会丢失)。
  • 每次点击按钮,count 增加 → 触发 Counter 重组 → Text 显示新数字。

重组的特点

  • 智能增量更新:只有读取了变化状态的 Composable 才会被重组。
  • 跳过不必要的工作:如果一个 Composable 的参数没有变化,Compose 会跳过它的执行(利用 @Stable 注解判断)。
  • 重组是乐观的:可能会被取消(例如多次快速点击时,中间状态可能被跳过)。

1.3 声明式思维的关键转变

传统思维 声明式思维
拿到 View 对象,修改它的属性 定义 UI = f(state),状态变了 UI 自动更新
手动管理 UI 树的增删 用条件语句(ifwhen)描述 UI 是否存在
用 Adapter 通知 RecyclerView 刷新 LazyColumn + items 自动响应列表变化
手动监听生命周期 LaunchedEffectDisposableEffect 处理副作用

1.4 副作用:在声明式中安全地做"不声明"的事

Composable 应该是幂等的(相同输入 → 相同输出),不应该直接在里面执行网络请求、数据库写入等副作用。这些需要放到效应(Effect)中。

@Composable
fun UserProfile(userId: String) {
    var user by remember { mutableStateOf<User?>(null) }

    LaunchedEffect(userId) {  // 当 userId 变化时启动协程
        user = api.fetchUser(userId)
    }

    user?.let { Text(it.name) } ?: CircularProgressIndicator()
}

常见的 Effect:

  • LaunchedEffect(key):在 key 变化时启动协程,自动取消上一次协程。
  • DisposableEffect(key):需要清理资源的副作用(如注册监听器)。
  • SideEffect:每次重组都会执行的副作用(不常用)。
  • rememberCoroutineScope:获取一个可以在 Composable 外部启动协程的作用域(如在 onClick 中启动)。

DisposableEffect 实战示例

@Composable
fun UserStatusListener(userId: String, onStatusChanged: (String) -> Unit) {
    // 当 userId 变化时,先 onDispose 清理旧监听器,再注册新监听器
    // 当 Composable 离开组合时,自动调用 onDispose 清理
    DisposableEffect(userId) {
        val listener = UserStatusListener { newStatus ->
            onStatusChanged(newStatus)
        }
        registerUserStatusListener(userId, listener)
        Log.d(TAG, "注册监听器: userId=$userId")

        onDispose {
            unregisterUserStatusListener(userId, listener)
            Log.d(TAG, "注销监听器: userId=$userId")
        }
    }
}

DisposableEffect 的核心价值:Compose 中没有 onDestroy() 回调,onDispose 就是你的清理时机。任何需要在离开界面时手动释放的资源(监听器、Observer、定时器),都应该放在 DisposableEffect 中。

posted @ 2026-06-21 19:49  yuansir0731  阅读(4)  评论(0)    收藏  举报