Jetpack Compose 与传统 View 系统的无缝混用

729 阅读3分钟

为什么要进行混用?

主要原因有这几点:

  1. 一个庞大的现有项目肯定不是立马动手将所有的代码都使用 Compose 重写,而是会逐步迁移,选择将新的模块或界面使用 Compose,然后再逐步将旧的 View 界面和组件替换为使用 Compose 实现,最终完成项目UI框架的替换。

  2. 有些自定义 View 的组件已经很完善了,并且很复杂。一时要将这个组件替换为使用 Compose 实现,是需要耗费大量时间、精力的,所以我们一般可以复用这些 View 组件,等到以后有时间、有机会了,再去重写它。

  3. 有些 View(如 SurfaceViewTextureView) 在 Compose 里面没有对等的实现,这时,就必须在 Compose 使用这些 View。

综合以上原因,进行混用是必须的。有时要在 Composable 函数中使用 View 的组件,有时要在传统 View 布局中嵌入 Composable 函数,我们就来看这两种方式怎么实现。

在传统 View 系统中使用 Compose

我们想要在 View 布局中引入 Compose,但Compose 和 View 是两套系统,该怎么进行融合、混用呢?

使用 ComposeView,它是一个继承了 ViewGroup 的类,可以装载 Composable 函数。

在使用代码构建的布局中动态添加 ComposeView

比如现在有一个 LinearLayout 布局,我们想将我们的自定义 Composable 函数放进去,我们可以这样:

// 线性布局
val linearLayout: LinearLayout = LinearLayout(this)

val composeView = ComposeView(context = this).apply {

    // 提供 Composable 的环境
    setContent {
        Column {
            Text(text = "我是 androidx.compose.material3 包下的 Text 组件")

            Button(onClick = {}) {
                Text("平平无奇的按钮")
            }
        }
    }
}

linearLayout.addView(
    composeView,
    ViewGroup.LayoutParams(
        ViewGroup.LayoutParams.MATCH_PARENT,
        ViewGroup.LayoutParams.WRAP_CONTENT
    )
)

在 XML 布局文件中添加

你也可以在 xml 布局文件中声明 ComposeView。

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

在代码中获取到这个 ComposeView,然后往里面添加 Composable 组件。

val composeViewInXml = findViewById<ComposeView>(R.id.composeView)
composeViewInXml.setContent {
    Column {
        Text(text = "我是 androidx.compose.material3 包下的 Text 组件")

        Button(onClick = {}) {
            Text("平平无奇的按钮")
        }
    }
}

在 Compose 中使用传统 View

反过来,我们想要在 Compose 里使用 View 的组件该怎么办?

使用 AndroidView Composable 函数,它允许我们往 Compose 中嵌入一个传统的 View,最少参数的函数原型:

@Composable
@UiComposable
fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
) 

它有两个重要参数:

  • 参数 factory:lambda 表达式会在首次组合时,被执行,我们可以在这里创建 View 实例。

  • 参数 update:lambda 表达式会在首次组合以及后续的重组过程中被执行,参数是在 factory 中创建的 View 实例,我们可以在这里进行修改 View 实例的属性。

比如这个例子:

@Composable
fun UseViewInComposeSample() {
    var text by remember { mutableStateOf("我是平平无奇的文本") }

    val context = LocalContext.current

    Column {
        AndroidView(
            factory = {
                // 初始化 View 组件
                TextView(context).apply {
                    text = "我是 android.widget 包下的 TextView"
                }
            },
            update = {
                // 初始化并且更新显示的文本
                it.text = text
            }
        )
    }
}

在 Compose 嵌入了一个原生的 TextView,并且它的 text 属性值会随着 text 状态的变化而更新。

总结

Jetpack Compose 与传统 View 系统的混用并不难,我们可以很方便地使用 Compose 给我们提供的 ComposeViewAndroidView 来完成。