首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《Jetpack Compose:不是XML替换,是UI哲学的革命》

《Jetpack Compose:不是XML替换,是UI哲学的革命》

原创
作者头像
资源shanxueit.com
发布2026-08-19 11:59:05
发布2026-08-19 11:59:05
1390
举报

你以为你在写UI?不,你在描述"状态到界面的映射函数"。不再是"创建→更新→销毁",而是"状态变,UI自动变"。


一、可组合函数:UI的"原子"

Compose最基础的单位是@Composable函数。它不是传统意义上的"组件类",而是一个纯函数——输入状态,输出UI描述。

最基础的Compose UI(对比传统XML)

代码语言:javascript
复制
// 传统XML方式(命令式)
// activity_main.xml:
// <TextView android:id="@+id/tv_name" android:text="Hello" />
// 在Activity中:findViewById<TextView>(R.id.tv_name).text = "World"

// Compose方式(声明式)
@Composable
fun Greeting(name: String) {
    // 直接描述UI,不需要findViewById
    Text(
        text = "Hello $name!",
        fontSize = 24.sp,
        color = Color.Blue,
        modifier = Modifier.padding(16.dp)
    )
}

// 使用方式
@Composable
fun MainScreen() {
    Greeting(name = "Jetpack Compose")
}

工程要点拆解

  • 函数即UI:每个@Composable函数就是一个UI片段,可以无限组合
  • 参数驱动:UI完全由参数决定,没有内部可变状态(纯函数)
  • 无需ViewBinding:没有findViewById、没有setText,UI是"描述"出来的

组合的优势

代码语言:javascript
复制
@Composable
fun UserCard(user: User) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .background(Color.White, RoundedCornerShape(8.dp))
    ) {
        Row {
            Image(
                painter = rememberImagePainter(user.avatar),
                contentDescription = "Avatar",
                modifier = Modifier.size(48.dp).clip(CircleShape)
            )
            Spacer(modifier = Modifier.width(16.dp))
            Column {
                Text(text = user.name, fontSize = 18.sp, fontWeight = FontWeight.Bold)
                Text(text = user.email, fontSize = 14.sp, color = Color.Gray)
            }
        }
    }
}

组合 vs 继承:Compose抛弃了View的继承体系,改用组合方式。你不再需要继承LinearLayout、重写onMeasure,只需要把基础组件拼装起来。


二、状态与重组:Compose的"心脏"

Compose最革命性的设计是状态驱动UI。当状态变化时,受影响的可组合函数自动"重组"(Recompose)——只更新变化的部分,不是整个页面刷新。

计数器示例(展示状态驱动)

代码语言:javascript
复制
@Composable
fun Counter() {
    // mutableStateOf:Compose的"响应式变量"
    var count by remember { mutableStateOf(0) }
    
    Column(
        horizontalAlignment = Alignment.CenterHorizontally,
        modifier = Modifier.fillMaxSize()
    ) {
        Text(
            text = "点击次数: $count",
            fontSize = 30.sp
        )
        
        Button(
            onClick = { count++ }  // 状态变化触发重组
        ) {
            Text("点击增加")
        }
    }
}

状态三要素拆解

  • mutableStateOf:创建可观察状态,Compose会监听它的变化
  • remember:记住状态值,重组时不重新初始化(否则每次重组count都归零)
  • by委托:Kotlin属性委托,让count像普通变量一样读写(实际调用getValue/setValue

状态的"智能更新"

代码语言:javascript
复制
@Composable
fun SmartUpdate(name: String, age: Int) {
    // 只有name变化时,这个Text才重组
    Text(text = "Name: $name")
    
    // 只有age变化时,这个Text才重组
    Text(text = "Age: $age")
    
    // 如果name和age都没变,整个函数不执行
}

Compose编译器会分析每个@Composable函数,只重组依赖变化状态的代码块,不是整个函数。

状态提升(State Hoisting)

代码语言:javascript
复制
// 状态提升到父组件,实现数据单向流动
@Composable
fun ParentScreen() {
    var text by remember { mutableStateOf("") }
    
    ChildInput(
        value = text,
        onValueChange = { text = it }  // 子组件通过回调修改父状态
    )
}

@Composable
fun ChildInput(value: String, onValueChange: (String) -> Unit) {
    TextField(
        value = value,
        onValueChange = onValueChange,
        label = { Text("输入内容") }
    )
}

三、副作用:Compose的"后门"

Compose函数是纯函数,不能直接做网络请求、数据库操作、Toast弹窗。这些"副作用"需要放到特定作用域中。

LaunchedEffect:在可组合函数中执行协程

代码语言:javascript
复制
@Composable
fun UserProfile(userId: String) {
    var user by remember { mutableStateOf<User?>(null) }
    var isLoading by remember { mutableStateOf(true) }
    
    // LaunchedEffect:进入组合时启动协程,离开时自动取消
    LaunchedEffect(userId) {  // userId变化时重新执行
        isLoading = true
        try {
            user = fetchUserFromApi(userId)  // 挂起函数
        } catch (e: Exception) {
            // 错误处理
        } finally {
            isLoading = false
        }
    }
    
    if (isLoading) {
        CircularProgressIndicator()
    } else {
        user?.let { UserCard(it) }
    }
}

其他副作用API

代码语言:javascript
复制
@Composable
fun SideEffectDemo() {
    // DisposableEffect:需要清理的副作用(注册/注销监听器)
    DisposableEffect(Unit) {
        val listener = registerListener()
        onDispose {
            unregisterListener(listener)  // 离开时清理
        }
    }
    
    // SideEffect:每次重组都执行(不常用,用于和外部系统同步)
    SideEffect {
        Log.d("Compose", "重组了")
    }
    
    // rememberCoroutineScope:在非组合上下文中启动协程
    val scope = rememberCoroutineScope()
    Button(onClick = {
        scope.launch {
            // 点击时执行异步操作
            performHeavyOperation()
        }
    }) {
        Text("执行任务")
    }
}

四、布局与修饰符:Compose的"造型工具"

Compose抛弃了XML的match_parentwrap_content,改用Modifier链来定制UI外观和行为。

常用布局对比

代码语言:javascript
复制
@Composable
fun LayoutDemo() {
    // Column:垂直排列(相当于LinearLayout垂直)
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.SpaceEvenly,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("顶部")
        Text("中间")
        Text("底部")
    }
    
    // Row:水平排列
    Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween
    ) {
        Text("左")
        Text("中")
        Text("右")
    }
    
    // Box:叠加布局(相当于FrameLayout)
    Box(
        modifier = Modifier.size(200.dp),
        contentAlignment = Alignment.Center
    ) {
        Image(...)  // 背景
        Text("居中文字")  // 前景
    }
}

// Modifier链式调用(组合多个修饰符)
@Composable
fun StyledButton() {
    Text(
        text = "点击我",
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
            .background(Color.Blue, RoundedCornerShape(8.dp))
            .clickable { /* 点击事件 */ }
            .padding(24.dp),  // 内边距
        color = Color.White,
        textAlign = TextAlign.Center
    )
}

Modifier顺序很重要

代码语言:javascript
复制
// 顺序1:padding在外,background在内
Modifier
    .padding(16.dp)      // 先留外间距
    .background(Color.Red) // 再画背景(背景在padding内部)

// 顺序2:background在外,padding在内
Modifier
    .background(Color.Red) // 先画背景
    .padding(16.dp)      // 再留内间距(背景在padding外部)

// 视觉效果完全不同!

五、列表与滚动:Compose的"高性能渲染"

Compose用LazyColumn(懒加载列表)代替RecyclerView,但API简洁10倍。

代码语言:javascript
复制
@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        // items:渲染列表
        items(users) { user ->
            UserCard(user)
        }
        
        // item:渲染单个元素
        item {
            Text("列表头部", style = MaterialTheme.typography.h5)
        }
        
        // stickyHeader:吸顶标题
        stickyHeader {
            Text("A组", modifier = Modifier.background(Color.White))
        }
        
        // 分页加载
        items(
            items = users,
            key = { it.id }  // 给每个item唯一key,优化重组
        ) { user ->
            UserCard(user)
        }
    }
}

LazyColumn的性能优化

代码语言:javascript
复制
@Composable
fun OptimizedList(users: List<User>) {
    LazyColumn {
        items(
            items = users,
            key = { user -> user.id }  // 1. 提供稳定key
        ) { user ->
            // 2. 每个item独立可组合,不影响其他item
            UserCard(
                user = user,
                modifier = Modifier.animateItemPlacement()  // 3. 动画自动处理
            )
        }
    }
}

六、主题与样式:Compose的"设计系统"

Compose内置Material Design 3支持,用MaterialTheme统一管理颜色、字体、形状。

代码语言:javascript
复制
// 自定义主题
@Composable
fun MyAppTheme(content: @Composable () -> Unit) {
    val colors = lightColors(
        primary = Color(0xFF6200EE),
        secondary = Color(0xFF03DAC6),
        background = Color(0xFFF5F5F5)
    )
    
    MaterialTheme(
        colors = colors,
        typography = Typography(
            h1 = TextStyle(fontSize = 30.sp, fontWeight = FontWeight.Bold),
            body1 = TextStyle(fontSize = 16.sp)
        ),
        shapes = Shapes(
            small = RoundedCornerShape(4.dp),
            medium = RoundedCornerShape(8.dp),
            large = RoundedCornerShape(16.dp)
        )
    ) {
        content()
    }
}

// 使用主题
@Composable
fun ThemedText() {
    Text(
        text = "Hello World",
        style = MaterialTheme.typography.h1,
        color = MaterialTheme.colors.primary
    )
}

// 支持深色模式
@Composable
fun AutoTheme(content: @Composable () -> Unit) {
    val isDark = isSystemInDarkTheme()
    val colors = if (isDark) darkColors() else lightColors()
    MaterialTheme(colors = colors, content = content)
}

七、Compose的"道":抛弃View思维,拥抱函数式UI

传统View体系

代码语言:javascript
复制
状态变化 → findViewById → setText/setVisibility → 手动更新UI

Compose体系

代码语言:javascript
复制
状态变化 → 自动重新执行@Composable函数 → 差异化更新UI

核心思想转换

  1. "怎么更新" → "状态是什么":不用想如何更新TextView,只需改变状态变量
  2. "继承" → "组合":不用继承LinearLayout,把Column、Row、Box组合起来
  3. "生命周期" → "重组":不用onCreate/onResume,理解重组何时发生

最后一句忠告

Compose不是"另一种XML",它是"另一种思考方式"。如果你还在找findViewById、还在想notifyDataSetChanged,说明你还没放下过去的包袱。真正掌握Compose的标志是——当需求变化时,你最先想到的是"改哪个状态变量",而不是"改哪个UI控件"


附加:调试技巧(重组计数)

代码语言:javascript
复制
@Composable
@Preview
fun DebugRecomposition() {
    // 在Logcat中查看重组次数
    val recompositionCount = remember { mutableStateOf(0) }
    SideEffect {
        recompositionCount.value++
        Log.d("Compose", "重组次数: ${recompositionCount.value}")
    }
    Text("重组了 ${recompositionCount.value} 次")
}

看到疯狂重组时,检查是否在@Composable中直接创建了mutableStateOf(没包remember),或者lambda表达式创建了新对象导致重组死循环。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、可组合函数:UI的"原子"
  • 二、状态与重组:Compose的"心脏"
  • 三、副作用:Compose的"后门"
  • 四、布局与修饰符:Compose的"造型工具"
  • 五、列表与滚动:Compose的"高性能渲染"
  • 六、主题与样式:Compose的"设计系统"
  • 七、Compose的"道":抛弃View思维,拥抱函数式UI
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档