15、Compose 与 View 互操作:AndroidView 嵌入传统 View,ComposeView 在传统布局中使用,Fragment 与 Compose 混合

说实话,Jetpack Compose 出来这么多年了,很多项目还是「新旧混搭」的状态。你不可能一夜之间把整个项目全改成 Compose,对吧?我自己的经验是,大部分团队都是渐进式迁移——新页面用 Compose,老页面继续用 View,两边还得能互相调用。

这一章,我们就来聊聊 Compose 和传统 View 怎么「和平共处」。说白了,就是三个场景:在 Compose 里用 View在 View 里用 Compose在 Fragment 里用 Compose。嗯,这三个场景搞明白了,你就能在项目里灵活切换了。

核心要点:Compose 和 View 互操作不是「二选一」,而是「互相嵌套」。AndroidView 和 ComposeView 就是两座桥梁,一个从 Compose 通往 View,一个从 View 通往 Compose。

15.1 为什么需要互操作?

你可能要问:既然 Compose 这么强大,为什么还要回头用 View?

原因其实很简单。我在项目中遇到过好几次这样的情况:某个第三方 SDK 只提供了 View 组件,比如地图控件、视频播放器、WebView 等等。这些组件没有 Compose 版本,你总不能自己重写一个吧?

另外,老项目里可能有大量写好的自定义 View,重构成本太高。渐进式迁移才是明智的选择。

所以,互操作不是「妥协」,而是「务实」。你想想看,一个成熟的 App,哪能说改就全改?

15.2 在 Compose 中使用传统 View:AndroidView

AndroidView 是 Compose 提供的一个 Composable 函数,专门用来把传统 View 嵌入到 Compose 布局中。它的用法非常直观:

@Composable
fun MapViewComposable() {
    // 在 Compose 中嵌入一个 MapView
    AndroidView(
        factory = { context ->
            MapView(context).apply {
                // 初始化配置
                isClickable = true
            }
        },
        update = { mapView ->
            // 当状态变化时更新 View
            mapView.onResume()
        },
        modifier = Modifier.fillMaxSize()
    )
}

这里有几个关键点,我跟你分享一下我的经验:

  • factory 参数:只会在首次组合时调用一次,用来创建 View 实例。别在这里做频繁更新的操作。
  • update 参数:每次重组时都会调用,适合更新 View 的状态。比如地图的缩放级别、标记点等。
  • modifier 参数:可以像普通 Compose 组件一样设置布局约束。

小技巧:如果你需要在 Compose 中频繁更新 View 的属性,记得把更新逻辑放在 update 块里,而不是 factory 里。我曾经犯过这个错——在 factory 里设置了一次性属性,结果后面状态变了 View 没反应,排查了半天。

15.3 在传统 View 中使用 Compose:ComposeView

反过来,如果你在 XML 布局里想用 Compose,那就需要 ComposeView 了。它本质上是一个 ViewGroup,里面可以承载 Compose 内容。

在 XML 中声明:

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

在 Activity 或 Fragment 中设置内容:

val composeView = findViewById<ComposeView>(R.id.compose_view)
composeView.setContent {
    MaterialTheme {
        Text(
            text = "这是 Compose 内容",
            style = MaterialTheme.typography.h5
        )
    }
}

嗯,这里要注意一个坑:ComposeView 的生命周期。它需要和宿主 Activity 或 Fragment 的生命周期绑定。如果你在 Fragment 里用,建议在 onViewCreated 中设置内容,而不是 onCreateView。

避坑指南:我曾经在 Fragment 的 onCreateView 里直接调用了 composeView.setContent(),结果导致 Compose 内容重复创建,界面闪烁。后来改成在 onViewCreated 中调用,问题就解决了。原因很简单——onCreateView 可能会被多次调用,而 onViewCreated 只调用一次。

15.4 Fragment 与 Compose 混合使用

Fragment 和 Compose 混合,其实有两种常见模式:

模式 说明 适用场景
Fragment 承载 Compose Fragment 的布局中嵌入 ComposeView 渐进式迁移,老页面逐步替换
Compose 中使用 Fragment 在 Compose 中通过 AndroidView 嵌入 FragmentContainerView 需要复用已有的 Fragment 逻辑

我个人更推荐第一种模式——Fragment 作为「壳」,内部用 Compose 实现 UI。这样 Fragment 的生命周期管理照旧,UI 层却可以享受 Compose 的便利。

举个例子:

class MyFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        return ComposeView(requireContext()).apply {
            setContent {
                MyComposeScreen()
            }
        }
    }
}

你看,Fragment 的 onCreateView 直接返回一个 ComposeView,整个 Fragment 的 UI 就完全由 Compose 接管了。这样 Fragment 的导航、生命周期回调依然有效,但 UI 层已经「Compose 化」了。

我的建议:如果你在做一个新功能,直接用 Compose 写 Fragment 的 UI。如果是在老功能里加新模块,用 ComposeView 嵌入到现有 XML 布局中。这两种方式都不需要动太多现有代码。

15.5 知识体系结构图

下面这张图帮你理清 Compose 和 View 互操作的几种关系:

Compose 与 View 互操作知识体系 互操作桥梁 AndroidView 在 Compose 中嵌入传统 View 适用:地图、WebView、自定义View ComposeView 在 XML 布局中嵌入 Compose 适用:渐进式迁移、新模块嵌入 Fragment + Compose 混合模式

15.6 实际项目中的最佳实践

说了这么多理论,来点实际的。我在一个电商项目里做过一次迁移,总结了几条经验:

  1. 从底部开始:先替换那些独立的、不依赖太多业务逻辑的 UI 组件,比如列表项、卡片。
  2. 保持接口一致:如果 View 和 Compose 需要共享数据,用 ViewModel 做中间层,两边都观察同一个 LiveData 或 StateFlow。
  3. 注意性能:AndroidView 每次重组都会调用 update 块,如果 update 里做了耗时操作,会影响帧率。我建议把频繁更新的逻辑用 LaunchedEffect 或 SideEffect 隔离。
  4. 测试要跟上:混合架构的测试比纯 Compose 或纯 View 都复杂。我习惯用 ComposeTestRule 测试 Compose 部分,用 Espresso 测试 View 部分,两边分开测。

避坑指南:我曾经在 Compose 里用 AndroidView 嵌入了一个 RecyclerView,结果发现 RecyclerView 的滚动和 Compose 的滚动冲突了。解决方案是给 AndroidView 设置 Modifier.verticalScroll(rememberScrollState()) 为 disabled,让 RecyclerView 自己处理滚动。

15.7 总结

Compose 和 View 互操作,说白了就是两座桥:AndroidView 让你在 Compose 里用 View,ComposeView 让你在 View 里用 Compose。Fragment 作为中间层,可以灵活地承载 Compose 内容。

我个人觉得,互操作不是「过渡方案」,而是「长期共存方案」。即使未来 Compose 完全成熟了,有些场景(比如地图、视频播放)可能还是需要 View 组件。所以,掌握好这两座桥,你的技术栈会更灵活。

嗯,这一章的内容就到这里。代码不多,但思路很重要。你想想看,如果你的项目里能自由地在 Compose 和 View 之间切换,那迁移起来是不是就轻松多了?


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