近日,在 CMP 的 1.10 更新里,官方提及了两个关于 Interop views 的全新特性:Overlay 和 Autosizing ,核心目的就是解决 Compose Multiplatform 在"嵌入原生 UI 组件(interop views)"时最头疼的两件事:层级(z-order)和尺寸(measure/layout)。
Interop View ≈ Flutter 里的 PlatformView 。
一、Overlay
Overlay placement for interop views 的主要作用就是:让 UIKit interop 真正能"盖在"Compose 上 :
kotlin
@OptIn(ExperimentalComposeUiApi::class)
UIKitViewController(
modifier = modifier,
factory = { factory.createNativeMap() },
update = {},
properties = UIKitInteropProperties(placedAsOverlay = true)
)
在过去,我们可以使用 UIKitView / UIKitViewController 把 iOS 原生控件嵌到 Compose 里,而这次 1.10 增加了一个 实验性开关:UIKitInteropProperties.placedAsOverlay = true,主要目的就是让这些 UIKit interop 视图可以放在 Compose UI 之上。
核心目的就是 interop view 可以更好地支持透明背景和原生 shader / 特效 ,这对于现在全新的 iOS 26 Liquid Glass 场景来说尤为重要。
简单来说,在之前,iOS 上的 interop view 更像 Underlay ,不严谨的说,就类似 : UIKit 在下,Compose 在上,Compose 需要"让出一个洞/区域"让 UIKit 露出来 。
因为 Compose (Skia) 和 UIKit view 不在同一渲染树里,所以默认 interop 实现更偏向"在 Compose 里留出空间让 UIKit 显示",所以视觉效果就像 hole punching。
这种情况下 iOS 的 Interop views 无法实现毛玻璃/半透明覆盖,因为原生 View 在下面,你无法让原生 View 变成半透明盖在 Compose 内容上。
所以你会发现,在之前,当你需要 blur 的时候,看到的不是 Compose 而是黑底或 window 底色" ,这是因为 "UIKit 的半透明/blur 采样不到 Skia 的内容" 。
而现在 1.10 提供了新的可能,interop view 可以通过配置提升到 Compose/Skia 渲染层上方成为真正的 overlay,这时候原生半透明背景、shader、blur 等效果就可以正确叠加在 Compose 上,当然,同时代价是同区域 Compose 可能会被遮挡/点击被原生 view 吃掉。
虽然实现上不是 Flutter HC 那种合成路径,但是表现形式上和类似。
总结一下就是:之前 iOS 上的 interop view 只能做"底图",不能做"蒙层" ,现在可以开始追求 iOS 原生质感的效果了。
Autosizing
从 1.10 开始,CMP 对 interop view 加了 Autosizing :
- Desktop(Swing) :
SwingPanel会根据嵌入 Swing 组件的minimum / preferred / maximum size自动调整自身尺寸 - iOS(UIKit) :UIKit interop view 支持按视图的 fitting size / intrinsic content size 来决定大小,从而能更好地 wrap content
简单说就是:你不用再手算尺寸、也不用提前写死宽高。
并且特别提到一个大痛点的解法:在 iOS 上,这让你通过 UIHostingController 包 SwiftUI 的时候,更容易做到正确的"内容包裹"。
为什么这又是一个大更新内容?因为在 Compose 的世界里,布局是"先测量、再布局",而过去 interop view 最大的问题是:
- Compose 不知道原生 view 的"理想尺寸",导致常常必须:
- 给
Modifier.size(...)写死; - 或者自己去桥接 native 的测量逻辑,再把结果喂给 Compose
- 给
- 一旦内容变化(比如 SwiftUI 文本变化、Swing 组件 preferredSize 变了),还要触发重算
Autosizing 相当于把 interop 的测量链路补齐了,让 interop 更接近 Compose 原生组件的体验:能 wrap content,能随内容变化而重新测量。
这个基础上,SwiftUI 视图(通过
UIHostingController)和不依赖NSLayoutConstraints的 basicUIView子类能更方便适配封装。
也就是说这个更新,可以简化了混合开发时的布局工作,让原生组件能像 Compose 原生组件一样自然地"撑开"布局。
最后
至于你肯定会说,那为什么不说 Andorid ?那肯定是因为 CMP 是基于 Compose 实现的啊,Compose Android 本质上还是嵌在 Android View 系统里,ComposeView 是一个 Android View,跑在同一个 View hierarchy 里:
Compose 和 Android View 一开始就属于同一个"生态",同一种坐标系、同一种生命周期、同一种事件分发。
所以当你用 AndroidView {} 嵌一个原生 View 的时候,它没有"Skia vs UIKit 两个体系隔离"这种割裂,最典型的就是 SurfaceView 在 AndroidView 里可以正常工作:
Compose 和「传统 View」 共用同一个 Window 和
DecorView,AndroidView作为一个桥接节点,将「传统 View」 "插入" 到 Compose 的布局树中,虽然SurfaceView绘制内容是独立的,但在屏幕上是共享一个Window,SurfaceFlinger依然会统一管理窗口合成。
这部分在之前的 《深入 Flutter 和 Compose 的 PlatformView 实现对比,它们是如何接入平台控件》 有了聊过,感兴趣可以看看。
参考链接
https://kotlinlang.org/docs/multiplatform/whats-new-compose-110.html