1. 引言
随着业务复杂度的增加,完全使用 Flutter 渲染 UI 可能无法满足所有需求。比如你需要接入极光推送的消息展示、复杂的原生地图组件(高德/百度地图)、或者原生 WebView。 这时,我们需要将原生的 Android 或 iOS UI 组件"挖个洞"嵌到 Flutter 视图层级中,这就引出了 Flutter 的 Platform Views(AndroidView 和 UiKitView)。本文将解析其原理及手势冲突的处理。
2. 核心技术概念
Platform Views 允许在 Flutter 的 Widget 树中直接嵌入 Native 视图。
演进与架构 (ASCII 示意)
sql
Flutter Widget Tree
|
Container
|
AndroidView / UiKitView
|
+-----------------------------------+
| Native Android/iOS View Hierarchy |
| (e.g. MapView, WebView) |
+-----------------------------------+
对于 Android,Flutter 经历了 Virtual Display (虚拟显示) 到 Hybrid Composition (混合组合) 甚至到新版 Texture Layer Hybrid Composition (TLHC) 的演变。
- Virtual Display:渲染到内存纹理,手势需要手动映射,容易产生键盘和无障碍功能的 bug。
- Hybrid Composition:直接将原生 View 加入到 Flutter 的 View 树中,完美支持无障碍、键盘,但以前每加一层会有一定的性能损耗。 在最新的 Flutter 版本中,引擎会自动判断使用哪种最佳模式(通常底层默认是 TLHC,平衡了性能与兼容性)。
3. 典型代码示例
3.1 Flutter 侧:使用 AndroidView
dart
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class NativeMapView extends StatelessWidget {
@override
Widget build(BuildContext context) {
const String viewType = 'com.example.app/mapview';
final Map<String, dynamic> creationParams = <String, dynamic>{
'initLocation': 'Beijing',
};
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
// 处理手势冲突的关键
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
gestureRecognizers: <Factory<OneSequenceGestureRecognizer>>{
Factory<OneSequenceGestureRecognizer>(
() => EagerGestureRecognizer(),
),
},
);
}
return Text('Platform not supported');
}
}
3.2 Android 侧注册组件
kotlin
import android.content.Context
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import io.flutter.plugin.common.StandardMessageCodec
class NativeMapFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, id: Int, args: Any?): PlatformView {
val creationParams = args as? Map<String?, Any?>?
return NativeMapViewImpl(context, id, creationParams)
}
}
class NativeMapViewImpl(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView = TextView(context)
init {
val location = creationParams?.get("initLocation") as? String ?: "Unknown"
textView.text = "Native View Map at $location"
textView.setBackgroundColor(android.graphics.Color.YELLOW)
}
override fun getView(): View {
return textView
}
override fun dispose() {}
}
千万别忘了在 MainActivity 中注册 Factory!
4. 解决手势冲突与避坑指南
当你将一个可滑动的原生组件(如地图)放在一个可滑动的 Flutter 组件(如 ListView、SingleChildScrollView)内部时,必然发生手势竞争。
手势竞争解决方案:EagerGestureRecognizer
如上面代码所示,给 AndroidView 传入 gestureRecognizers 并配置 EagerGestureRecognizer。 EagerGestureRecognizer 会在 Flutter 的手势竞技场中"立即获胜",强制将所有触摸事件分发给内部的原生视图。 这意味着,只要用户按在这个 Native 视图区域,滑动事件就不会被外层的 ListView 拦截,而是直接被内部的原生地图处理。
性能与最佳实践
- 控制数量 :PlatformView 虽然好用,但创建与销毁开销大。不要在
ListView.builder中大批量生成 PlatformView。 - 生命周期同步:当 Flutter 路由切换时,注意原生组件的暂停与恢复,避免后台播放声音或持续定位导致耗电。
- 混合层级覆盖:有时 Flutter 弹出弹窗 (Dialog) 可能会被 Native View 遮挡。确保在 Flutter 3.x 后,采用 Hybrid Composition 可以极大缓解这种 Z-Index 问题。
5. 总结
Platform Views 让 Flutter 如虎添翼,轻松复用庞大的 Native 生态。虽然配置略显繁琐,但只要掌握了视图的注册机制以及通过 gestureRecognizers 解决手势拦截的技巧,就能优雅地在两端无缝穿梭。