一、前言
在 Compose 中,经常需要复用已有的 Android 原生组件(如 WebView、SurfaceView、MapView 等),或者使用一些尚未被 Compose 重新实现的第三方库。Compose 提供了 AndroidView 这个 Composable 函数来实现 Compose 组件与原生 View 的混排。
本文将深入源码,详细解析 AndroidView 是如何将原生 View 嵌入到 Compose 树中的,以及原生组件的测量(Measure)、布局(Layout)和绘制(Draw) 是如何与 Compose 的渲染流程桥接的。
二、 AndroidView 的入口与节点创建
先看 AndroidView 的源码入口:
@Composable
@UiComposable
fun <T : View> AndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
onReset: ((T) -> Unit)? = null,
onRelease: (T) -> Unit = NoOpUpdate,
update: (T) -> Unit = NoOpUpdate,
) {
// 1. 获取当前环境的各种 Local 值
val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
val materializedModifier = currentComposer.materialize(modifier.focusInteropModifier())
val density = LocalDensity.current
val layoutDirection = LocalLayoutDirection.current
val compositionLocalMap = currentComposer.currentCompositionLocalMap
val lifecycleOwner = LocalLifecycleOwner.current
val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
// 2. 创建 ComposeNode (或 ReusableComposeNode)
if (onReset != null) {
ReusableComposeNode<LayoutNode, UiApplier>(
factory = createAndroidViewNodeFactory(factory),
update = {
// ... 更新参数
},
)
} else {
ComposeNode<LayoutNode, UiApplier>(
factory = createAndroidViewNodeFactory(factory),
update = {
// 3. 将参数更新到 ViewHolder 中
updateViewHolderParams<T>(
modifier = materializedModifier,
compositeKeyHash = compositeKeyHash,
density = density,
lifecycleOwner = lifecycleOwner,
savedStateRegistryOwner = savedStateRegistryOwner,
layoutDirection = layoutDirection,
compositionLocalMap = compositionLocalMap,
)
set(update) { requireViewFactoryHolder<T>().updateBlock = it }
set(onRelease) { requireViewFactoryHolder<T>().releaseBlock = it }
},
)
}
}
AndroidView 本质上是一个 ComposeNode,它在 Compose 树中对应一个 LayoutNode。关键在于 createAndroidViewNodeFactory(factory) 这个工厂方法:
@Composable
private fun <T : View> createAndroidViewNodeFactory(factory: (Context) -> T): () -> LayoutNode {
val compositeKeyHash = currentCompositeKeyHashCode.hashCode()
val context = LocalContext.current
val parentReference = rememberCompositionContext()
val stateRegistry = LocalSaveableStateRegistry.current
val ownerView = LocalView.current
return {
// 创建 ViewFactoryHolder,并返回它的 layoutNode
ViewFactoryHolder(
context = context,
factory = factory,
parentContext = parentReference,
saveStateRegistry = stateRegistry,
compositeKeyHash = compositeKeyHash,
owner = ownerView as Owner,
).layoutNode
}
}
这里创建了一个 ViewFactoryHolder,它是 AndroidViewHolder 的子类。AndroidViewHolder 是连接 Compose 和原生 View 的核心桥梁。
三、 核心桥梁:AndroidViewHolder
AndroidViewHolder 继承自 ViewGroup,它扮演了两个角色:
- 原生 View 的容器:它是一个
ViewGroup,通过addView(view)将我们传入的factory创建的原生 View 添加为自己的子 View。 - Compose 树的节点代理:它内部维护了一个
LayoutNode,这个LayoutNode被插入到 Compose 树中,代理了 Compose 的测量、布局和绘制请求,并将它们转发给内部的原生 View。
internal open class AndroidViewHolder(
context: Context,
parentContext: CompositionContext?,
private val compositeKeyHash: Int,
private val dispatcher: NestedScrollDispatcher,
val view: View, // 我们要嵌入的原生 View
private val owner: Owner,
) : ViewGroup(context), ... {
init {
// 将原生 View 添加到这个 ViewGroup 中
@Suppress("LeakingThis") addView(view)
// ...
}
// 内部维护的 LayoutNode,它会被返回给 ComposeNode 并插入到 Compose 树中
val layoutNode: LayoutNode = run {
val layoutNode = LayoutNode()
layoutNode.interopViewFactoryHolder = this@AndroidViewHolder
// 核心:配置 Modifier,桥接绘制和布局
val coreModifier = Modifier
// ...
.drawBehind {
// 桥接绘制
drawIntoCanvas { canvas ->
if (view.visibility != GONE) {
isDrawing = true
(layoutNode.owner as? AndroidComposeView)?.drawAndroidView(
this@AndroidViewHolder,
canvas.nativeCanvas,
)
isDrawing = false
}
}
}
.onGloballyPositioned {
// 桥接位置更新
layoutAccordingTo(layoutNode)
// ... 更新 View 的位置
}
layoutNode.modifier = modifier.then(coreModifier)
// 桥接测量和布局
layoutNode.measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints,
): MeasureResult {
// ... 测量逻辑
}
// ...
}
// 桥接 Attach/Detach
layoutNode.onAttach = { owner ->
(owner as? AndroidComposeView)?.addAndroidView(this, layoutNode)
if (view.parent !== this) addView(view)
}
layoutNode.onDetach = { owner ->
(owner as? AndroidComposeView)?.removeAndroidView(this)
removeAllViewsInLayout()
}
layoutNode
}
}
接下来,我们详细看看测量、布局和绘制是如何桥接的。
四、 测量(Measure)的桥接
当 Compose 树进行测量时,会调用 LayoutNode 的 measurePolicy。在 AndroidViewHolder 中,这个 measurePolicy 被重写了:
layoutNode.measurePolicy = object : MeasurePolicy {
override fun MeasureScope.measure(
measurables: List<Measurable>,
constraints: Constraints,
): MeasureResult {
if (childCount == 0) {
return layout(constraints.minWidth, constraints.minHeight) {}
}
// 1. 将 Compose 的 Constraints 转换为 Android 原生的 MeasureSpec
measure(
obtainMeasureSpec(
constraints.minWidth,
constraints.maxWidth,
layoutParams!!.width,
),
obtainMeasureSpec(
constraints.minHeight,
constraints.maxHeight,
layoutParams!!.height,
),
)
// 2. 返回测量结果,大小为 AndroidViewHolder 测量后的大小
return layout(measuredWidth, measuredHeight) {
layoutAccordingTo(layoutNode)
}
}
// ...
}
测量流程:
- Compose 传递下来
Constraints(包含最大最小宽高)。 obtainMeasureSpec方法将 Compose 的Constraints和原生 View 的LayoutParams(如MATCH_PARENT,WRAP_CONTENT)结合,转换为 Android 原生的MeasureSpec。- 调用
AndroidViewHolder(它是一个ViewGroup) 的measure()方法。 AndroidViewHolder的onMeasure会被触发:override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { // ... // 测量内部包裹的真正原生 View view.measure(widthMeasureSpec, heightMeasureSpec) // 设置 AndroidViewHolder 自身的大小为内部 View 的大小 setMeasuredDimension(view.measuredWidth, view.measuredHeight) // ... }- 最终,
LayoutNode获得了测量后的尺寸measuredWidth和measuredHeight,完成了 Compose 侧的测量。
五、 布局(Layout)的桥接
在上面的 measure 方法最后,调用了 layout(measuredWidth, measuredHeight) { layoutAccordingTo(layoutNode) }。这定义了 Compose 侧的布局行为。
当 Compose 确定了 LayoutNode 的位置后,会触发 onGloballyPositioned 修饰符,或者在 layout 块中执行 layoutAccordingTo(layoutNode)。
布局流程:
- Compose 确定了
LayoutNode在 Compose 树中的全局坐标。 layoutAccordingTo(layoutNode)会被调用(或者在onGloballyPositioned中触发)。这个方法会计算出LayoutNode相对于根视图(AndroidComposeView)的坐标。AndroidViewHolder会根据这些坐标,调用原生 View 的layout()方法:注意:override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { // 将内部的原生 View 布局到 AndroidViewHolder 的左上角,填满整个空间 view.layout(0, 0, r - l, b - t) }AndroidViewHolder自身的位置是由AndroidComposeView(Compose 的根 View) 通过addView和后续的布局操作来管理的。AndroidComposeView会根据LayoutNode的坐标,将AndroidViewHolder放置在屏幕的正确位置。
五、 绘制(Draw)的桥接
绘制的桥接是最巧妙的部分。Compose 的绘制是基于 Canvas 的,而原生 View 的绘制也是基于 Canvas 的,但它们属于不同的渲染体系。
在 AndroidViewHolder 的 LayoutNode 中,添加了一个 drawBehind 修饰符:
val coreModifier = Modifier
// ...
.drawBehind {
drawIntoCanvas { canvas ->
if (view.visibility != GONE) {
isDrawing = true
// 调用根视图 AndroidComposeView 的 drawAndroidView 方法
(layoutNode.owner as? AndroidComposeView)?.drawAndroidView(
this@AndroidViewHolder,
canvas.nativeCanvas,
)
isDrawing = false
}
}
}
绘制流程:
- 当 Compose 渲染树遍历到这个
LayoutNode时,会执行drawBehind块。 - 它获取到 Compose 的
Canvas,并提取出底层的原生android.graphics.Canvas(canvas.nativeCanvas)。 - 它调用
AndroidComposeView.drawAndroidView(),将AndroidViewHolder和原生Canvas传进去。 - 在
AndroidComposeView内部,实际上是调用了AndroidViewHolder的draw(canvas)方法(因为AndroidViewHolder是一个ViewGroup)。 - 这就触发了标准的原生 View 绘制流程:
AndroidViewHolder会将自己以及内部包裹的view绘制到 Compose 提供的这个Canvas上。
反向失效(Invalidate):
如果原生 View 内部发生了变化(例如动画、文字改变),它会调用 invalidate()。
AndroidViewHolder 重写了 onDescendantInvalidated 和 invalidateChildInParent:
override fun onDescendantInvalidated(child: View, target: View) {
super.onDescendantInvalidated(child, target)
invalidateOrDefer()
}
fun invalidateOrDefer() {
if (isDrawing) {
view.postOnAnimation(runInvalidate)
} else {
// 触发 Compose LayoutNode 的重绘
layoutNode.invalidateLayer()
}
}
这样,原生 View 的重绘请求就被转换成了 Compose LayoutNode 的重绘请求,保证了 UI 的同步更新。
七、 总结
AndroidView 实现 Compose 与原生 View 混排的核心原理可以概括为**“代理与桥接”**:
- 容器包装:Compose 创建一个
AndroidViewHolder(继承自ViewGroup) 作为原生 View 的容器。 - 节点代理:
AndroidViewHolder内部创建一个LayoutNode插入到 Compose 树中,作为 Compose 侧的代理。 - 测量桥接:Compose 的
Constraints被转换为原生MeasureSpec,传递给AndroidViewHolder进行原生测量,结果再返回给 Compose。 - 布局桥接:Compose 计算出
LayoutNode的位置,AndroidComposeView据此将AndroidViewHolder放置在正确位置,AndroidViewHolder再对其内部的原生 View 进行布局。 - 绘制桥接:Compose 绘制到该节点时,提供原生
Canvas,触发AndroidViewHolder的原生draw流程,将原生内容绘制到 Compose 的画布上。同时,原生 View 的invalidate会被拦截并转换为 Compose 的重绘请求。
通过这种精妙的设计,Compose 成功地将传统的 Android View 体系无缝地嵌入到了声明式的渲染管线中。
核心解惑:测量布局绘制究竟是 Compose 实现的还是原生实现的?
很多开发者会有疑问:既然嵌入了 Compose,这部分到底是谁在工作?答案是**“分工合作,相互桥接”**。
-
整体框架与边界由 Compose 决定:
- 这个 View 能占用的最大/最小空间(
Constraints)是由外层的 Compose 布局决定的。 - 这个 View 具体放在屏幕上的哪个 X/Y 坐标,也是由 Compose 计算完布局树后决定的。
- 这个 View 何时进行绘制、在 Z 轴的哪一层绘制(会不会被其他 Compose UI 遮挡),是由 Compose 的绘制管线统一调度的。
- 这个 View 能占用的最大/最小空间(
-
内部具体逻辑由原生 View 自己实现:
- 既然它是一个原生的
TextView、WebView或MapView,它具体需要多宽多高(文字多长、换几行)、它的子 View 如何摆放,依然是调用原生 Android View 的onMeasure()和onLayout()来计算的。 - 它的具体像素该怎么画(画文字、画网页、画地图),依然是走原生 View 的
onDraw(Canvas)方法。
- 既然它是一个原生的
-
桥接层(AndroidViewHolder)的工作:
- 测量时:Compose 把限制条件 (
Constraints) 翻译成原生能听懂的语言 (MeasureSpec) 交给原生 View。原生 View 算好后,桥接层把原生测好的具体宽高拿过来,报告回给 Compose。 - 绘制时:Compose 的渲染管线走到这一层时,把自己的画布底层 (
android.graphics.Canvas) 抽出来交给原生 View,让原生 View 拿着这块画布调用自己的draw(canvas),从而完美地将原生内容渲染到 Compose 的世界中。
- 测量时:Compose 把限制条件 (