首页 / Android 入门教程 / Compose 与 View 互操作

Android 入门教程

Compose 与 View 互操作

本教程共 100 篇 · 第 55 篇 · 更新于 2026-07-28 · 约 7 分钟阅读

AndroidAndroid 入门教程Jetpack ComposeAndroidViewComposeView互操作迁移

55. Compose 与 View 互操作

本节目标:学会在 Compose 中嵌入传统 View(AndroidView)、在 View 体系中嵌入 Compose(ComposeView),理解迁移策略和互操作注意事项。

为什么要互操作

很多项目不是从零开始用 Compose,而是从传统 View 体系逐步迁移。迁移过程中,新旧代码需要共存:

  • 新写的 Compose 页面里要嵌入一个还没迁移的老地图组件
  • 老的 Fragment 里想用 Compose 写一小块界面
  • 第三方库只提供 View,没有 Compose 版本

互操作 API 让两套体系能无缝混合。

AndroidView:在 Compose 里嵌入 View

AndroidView 把一个传统 View 嵌入 Compose 组合树:

@Composable
fun MapViewComposable() {
    AndroidView(
        factory = { context ->
            // 创建 View,只在首次进入组合时调用
            MapView(context).apply {
                onCreate(null)
                getMapAsync { googleMap ->
                    googleMap.moveCamera(
                        CameraUpdateFactory.newLatLngZoom(LatLng(39.9, 116.4), 10f)
                    )
                }
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}
  • factory:创建 View 的 lambda,只在首次组合时执行一次。
  • modifier:像普通 Compose 组件一样设置尺寸和位置。

用 update 响应状态变化

当 Compose 状态变化需要更新 View 时,用 update

@Composable
fun TextViewInCompose(text: String, color: Color) {
    AndroidView(
        factory = { context -> TextView(context) },
        update = { textView ->
            // 每次重组时调用,同步最新状态到 View
            textView.text = text
            textView.setTextColor(color.toArgb())
            textView.textSize = 16f
        },
        modifier = Modifier.fillMaxWidth()
    )
}
Note
  • factory 只在 View 创建时调用一次,update 每次重组都调用。创建放 factory,状态同步放 update。别在 update 里创建新 View。

嵌入 WebView

@Composable
fun WebViewComposable(url: String) {
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                webViewClient = WebViewClient()
                loadUrl(url)
            }
        },
        modifier = Modifier.fillMaxSize()
    )
}

ComposeView:在 View 体系里嵌入 Compose

反过来,在 XML 布局或 Fragment 里嵌入 Compose:

<!-- XML 布局里 -->
<androidx.compose.ui.platform.ComposeView
    android:id="@+id/compose_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
// 代码里设置内容
val composeView = findViewById<ComposeView>(R.id.compose_view)
composeView.setContent {
    MyTheme {
        Greeting("Android")
    }
}

或者直接在代码里创建:

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

ViewCompositionStrategy 生命周期策略

ComposeView 需要配置何时销毁 Compose 内容:

策略销毁时机
DisposeOnViewTreeLifecycleDestroyed父 ViewTree 的 LifecycleOwner 销毁时(默认)
DisposeOnLifecycleDestroyed指定的 Lifecycle 销毁时(需传入 Lifecycle 对象)
DisposeOnDetachedFromWindowView 从窗口分离时
composeView.setViewCompositionStrategy(
    ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
Warning
  • 在 Fragment 里用 ComposeView 时,推荐用 DisposeOnViewTreeLifecycleDestroyed。如果用 DisposeOnDetachedFromWindow,在 Fragment 的 View 被销毁但 Fragment 还活着时(比如加回返回栈),Compose 内容会被销毁,回来后重建可能丢失状态。

AbstractComposeView 自定义组合 View

如果要做一个可复用的「Compose 版自定义 View」:

class RatingBarView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {

    var rating by mutableStateOf(0f)
    var onRatingChange by mutableStateOf<(Float) -> Unit>({})

    @Composable
    override fun Content() {
        Row {
            repeat(5) { index ->
                Icon(
                    if (index < rating.toInt()) Icons.Default.Star
                    else Icons.Default.StarBorder,
                    contentDescription = null,
                    modifier = Modifier.clickable {
                        onRatingChange(index + 1f)
                    }
                )
            }
        }
    }
}

这样在 XML 里就能直接用 <RatingBarView />,属性通过代码设置。

互操作注意事项

主题不统一

Compose 的 MaterialTheme 和 View 的 XML 主题是两套体系。在 View 里嵌 Compose 时,记得给 Compose 内容套上主题:

// 正确:套主题
composeView.setContent {
    MyAppTheme {
        MyComposable()
    }
}

// 错误:没主题,颜色和字体不对
composeView.setContent {
    MyComposable()
}

性能

  • AndroidView 嵌入的 View 不会参与 Compose 的智能重组优化,每次 update 都会执行。
  • 频繁变化的状态驱动 AndroidView 可能比纯 Compose 慢。
  • 能用 Compose 原生组件替代的,尽量别用 AndroidView

生命周期和保存状态

AndroidView 嵌入的 View 的生命周期跟随组合。如果 View 需要保存状态(如 WebView 的滚动位置),用 rememberSaveable 配合 View 的 onSaveInstanceState

迁移建议

  1. 从叶子组件开始:先迁移独立的卡片、列表项等小组件,风险低。
  2. 新页面直接用 Compose:新功能用 Compose 写,老页面保持不变。
  3. 先 Fragment 后 Compose 导航:迁移初期继续用 Fragment 导航,每个 Fragment 内部用 ComposeView。全迁移完再换 Navigation Compose。
  4. 共用 ViewModel:Compose 和 View 可以共享同一个 ViewModel,数据层不用改。
Tip
  • 迁移是渐进的,别试图一次性全换。新旧共存期间,保持数据层统一(ViewModel + Repository),UI 层逐步替换。

小结

AndroidView 在 Compose 里嵌入 View,ComposeView 在 View 体系里嵌入 Compose,两者让渐进迁移成为可能。注意主题统一、生命周期策略、性能差异。迁移从叶子组件开始,新页面直接用 Compose,最终把导航也换成 Navigation Compose。下一节学习 Compose 性能优化。