11、Compose入门:Jetpack Compose基础、声明式UI、Compose与View互操作

说实话,第一次接触Jetpack Compose的时候,我心里是有点抵触的。毕竟在Android上写XML布局写了快十年,突然让我用Kotlin代码直接写UI,总觉得哪里不对。但真正上手之后,我发现这东西确实有点东西——它不仅仅是换了个写法,而是彻底改变了我们构建UI的思维方式。

声明式UI:和传统方式说再见

传统的Android UI开发,我们用的是命令式的方式。说白了就是:你先在XML里画好布局,然后在Activity里找到这些View,再手动设置数据、监听器。这个过程就像是在指挥一个木偶——你拉一下线,它动一下。

而Compose采用的是声明式UI。你只需要告诉框架「我想要什么」,剩下的交给框架去处理。举个例子:

// 传统方式
TextView textView = findViewById(R.id.text_view);
textView.setText("Hello");
textView.setOnClickListener(v -> { ... });

// Compose方式
@Composable
fun Greeting(name: String) {
    Text(
        text = "Hello $name",
        modifier = Modifier.clickable { ... }
    )
}

看到区别了吗?Compose里没有「找到View」这一步,也没有「设置文本」这一步。我们直接描述UI应该长什么样,数据变了UI自动更新。我在项目中遇到过很多次因为忘记更新UI导致的bug,用Compose之后这类问题少了很多。

核心思想:UI是状态的函数。状态变了,UI自动重建。你不需要手动管理UI的更新。

Compose基础:从Hello World开始

写一个Compose界面其实很简单。你只需要一个@Composable注解的函数,然后在里面调用其他Composable函数就行。就像搭积木一样。

@Composable
fun MyScreen() {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "欢迎来到Compose世界",
            fontSize = 24.sp,
            fontWeight = FontWeight.Bold
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(onClick = { /* 点击事件 */ }) {
            Text("点击我")
        }
    }
}

嗯,这里要注意几个点:

  • Modifier:这是Compose里最核心的概念之一。所有布局、样式、交互都通过它来配置。我个人习惯把Modifier放在参数的第一位,这样代码更清晰。
  • Column/Row/Box:这是Compose的三大布局组件,分别对应垂直排列、水平排列和层叠布局。
  • Spacer:用来占位,相当于传统布局里的Spacemargin

小技巧:刚开始写Compose时,你可能会觉得Modifier链式调用很别扭。我的建议是——先别管那么多,照着写就行。用不了两天你就会发现这玩意儿比XML的layout_xxx属性好用多了。

状态管理:让UI活起来

Compose的UI是「死」的,只有加上状态管理,它才能「活」起来。最常用的状态管理方式就是remembermutableStateOf

@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier.padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "计数: $count",
            fontSize = 20.sp
        )
        
        Button(onClick = { count++ }) {
            Text("增加")
        }
    }
}

这里有个关键点:remember保证了状态在重组时不会被重置,mutableStateOf则让Compose知道这个状态变了需要重新绘制UI。我曾经见过有人忘记用remember,结果每次点击按钮UI都不更新——嗯,那场面挺尴尬的。

避坑指南:千万不要在Composable函数里直接修改普通变量!比如var count = 0然后count++,这样UI是不会更新的。必须用mutableStateOf或者StateFlow这类可观察的数据容器。

Compose与View互操作:新老共存

现实项目中,你不可能一下子把所有页面都改成Compose。大多数团队都是渐进式迁移。这时候就需要Compose和传统View能互相嵌套。

在Compose中使用Android View

AndroidView这个Composable函数,你可以把任何传统View嵌入到Compose布局中:

@Composable
fun MapViewComposable() {
    AndroidView(
        factory = { context ->
            MapView(context).apply {
                // 配置MapView
            }
        },
        update = { mapView ->
            // 更新MapView
        }
    )
}

我在项目中用这个方式集成了百度地图SDK——因为百度地图当时还没有Compose版本。效果还不错,就是要注意生命周期管理。

在传统View中使用Compose

反过来,你也可以在XML布局里放一个ComposeView

<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

然后在Activity或Fragment里设置Compose内容:

findViewById<ComposeView>(R.id.compose_view).setContent {
    MyComposableFunction()
}

说实话,这种互操作能力是Compose能快速推广的重要原因。你不需要「All in」,可以慢慢来。

知识体系总览

下面这张图概括了本章的核心内容,我建议你保存下来,学完整个章节后再回来看一遍,会有更深的理解:

Jetpack Compose 入门知识体系 Jetpack Compose 声明式UI 基础组件 View互操作 状态驱动UI 自动重组 @Composable Column/Row/Box Modifier remember/State AndroidView ComposeView 渐进式迁移 · 新老共存 · 声明式思维

Compose的布局系统

Compose的布局和传统View的布局不太一样。它用的是「测量-布局-绘制」三步走,但整个过程是递归的。你想想看,每个Composable函数都会告诉父组件「我有多大」,父组件再根据所有子组件的大小决定怎么摆放。

传统View Compose
XML布局文件 Kotlin代码直接描述
findViewById 不需要,直接引用
手动更新UI 状态变化自动更新
LayoutParams Modifier配置
生命周期依赖Activity Composition生命周期

我个人觉得,Compose最大的优势不是「少写代码」,而是「逻辑更集中」。以前一个按钮的点击事件、样式、数据绑定可能分散在XML、Activity、ViewModel三个地方。现在全在一个Composable函数里,一眼就能看明白。

我的建议:刚开始学Compose,别急着上复杂项目。先拿一个简单的页面练手,比如登录页、个人中心页。等你习惯了「声明式」的思维方式,再回头看传统View,你会觉得那玩意儿怎么那么啰嗦。

好了,这一章的内容就到这里。记住:Compose不是银弹,但它确实是Android UI开发的未来。早点上手,早点受益。


公众号:蓝海资料掘金营,微信deep3321