自定义 Platform View:实现 PlatformViewFactory 与原生视图的生命周期桥接
大家好,今天我们要深入探讨 Flutter 中自定义 Platform View 的实现,重点是如何利用 PlatformViewFactory 将 Flutter 的生命周期事件桥接到原生视图,实现更精细的控制和交互。
Platform View 允许我们在 Flutter 应用中嵌入原生平台的 UI 组件,这对于复用现有原生代码、访问平台特定功能或者实现性能敏感的 UI 是非常有价值的。然而,原生视图的生命周期管理与 Flutter 的生命周期是分离的,因此需要一个桥梁来实现同步和控制。PlatformViewFactory 就是这个桥梁的关键组件。
Platform View 的基本概念
在深入 PlatformViewFactory 之前,我们先回顾一下 Platform View 的基本概念。一个 Platform View 主要包含以下几个部分:
- PlatformViewRegistry: 负责注册
PlatformViewFactory,将特定的 viewType 与工厂关联起来。 - PlatformViewFactory: 负责创建原生视图的实例。
- PlatformView: Flutter 侧的 Widget,它持有原生视图的引用,并负责将原生视图添加到 Flutter 的 Widget 树中。
- 原生视图: 实际的原生 UI 组件,比如 Android 上的
android.view.View或 iOS 上的UIView。 - MessageChannel: 用于 Flutter 和原生代码之间的通信。
Flutter 通过 PlatformView Widget 在 Flutter 的 Widget 树中占据一个位置,并通过 PlatformViewFactory 创建的原生视图填充这个位置。 当 Platform View Widget 被添加到 Widget 树中时,原生视图也会被添加到原生视图层级中。
PlatformViewFactory 的作用与接口
PlatformViewFactory 是一个抽象类,负责创建原生视图的实例。它定义了一个 create 方法,该方法接收一个 PlatformViewCreationParams 对象,并返回一个原生视图的实例。
abstract class PlatformViewFactory {
/// Creates a platform view with the given parameters.
dynamic create(PlatformViewCreationParams params);
}
abstract class PlatformViewCreationParams {
/// The unique identifier of the platform view.
int get id;
/// The view type of the platform view.
String? get viewType;
/// The parameters passed to the platform view.
dynamic get params;
}
params 参数可以是任何类型,用于传递初始化原生视图所需的数据。id 是一个唯一的标识符,用于在 Flutter 和原生代码之间识别特定的 Platform View 实例。viewType 是一个字符串,用于标识 Platform View 的类型,在注册 PlatformViewFactory 时会用到。
create 方法返回的是一个 dynamic 类型,这意味着它可以返回任何原生视图的实例。在 Android 上,它通常返回一个 android.view.View 对象,而在 iOS 上,它通常返回一个 UIView 对象。
PlatformViewFactory 的核心职责:
- 创建原生视图实例: 这是
PlatformViewFactory最核心的任务。根据传入的参数,创建并初始化原生视图。 - 初始化原生视图: 通常,在创建原生视图后,需要对其进行一些初始化操作,例如设置布局参数、添加事件监听器等。
- 管理原生视图的生命周期: 虽然 Flutter 无法直接控制原生视图的生命周期,但可以通过
PlatformViewFactory在视图创建和销毁时执行一些操作,例如释放资源。 - 建立 Flutter 与原生视图的通信通道:
PlatformViewFactory可以创建MethodChannel或EventChannel,用于 Flutter 和原生视图之间的双向通信。
实现 PlatformViewFactory:Android 示例
下面我们以 Android 为例,演示如何实现一个自定义的 PlatformViewFactory,并将其与原生视图的生命周期进行桥接。
假设我们要创建一个显示原生 Android TextView 的 Platform View。
首先,我们需要创建一个自定义的 PlatformViewFactory:
import android.content.Context
import android.view.View
import android.widget.TextView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
import io.flutter.plugin.common.StandardMessageCodec
class NativeTextViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView {
return NativeTextView(context!!, viewId, messenger, args)
}
}
class NativeTextView(context: Context, id: Int, messenger: BinaryMessenger, args: Any?) : PlatformView {
private val textView: TextView
init {
textView = TextView(context)
textView.textSize = 24f
textView.text = "Hello from Native TextView!"
// 从 args 中获取参数
if (args is Map<*, *>) {
val text = args["text"] as? String
if (text != null) {
textView.text = text
}
}
}
override fun getView(): View {
return textView
}
override fun dispose() {
// 在视图销毁时释放资源
println("NativeTextView disposed!")
}
}
代码解释:
NativeTextViewFactory类继承自PlatformViewFactory,并重写了create方法。create方法接收一个Context对象、一个viewId和一个args对象。viewId是 Platform View 的唯一标识符,args是从 Flutter 传递过来的参数。- 在
create方法中,我们创建了一个NativeTextView实例,并将Context、viewId、messenger和args传递给它。 NativeTextView类实现了PlatformView接口,并持有一个TextView对象。- 在
NativeTextView的构造函数中,我们创建了一个TextView实例,并对其进行了一些初始化操作,例如设置文本大小和文本内容。 我们还从args参数中获取文本内容,并将其设置到TextView中。 getView方法返回TextView对象,Flutter 会将这个View添加到 Flutter 的 Widget 树中。dispose方法在视图销毁时被调用,我们可以在这里释放资源。
原生视图的生命周期桥接:
- 创建:
create方法在 Flutter 创建 Platform View Widget 时被调用,这是原生视图生命周期的起点。我们可以在这里创建和初始化原生视图。 - 销毁:
dispose方法在 Flutter 销毁 Platform View Widget 时被调用,这是原生视图生命周期的终点。我们可以在这里释放资源,例如取消注册事件监听器、释放内存等。
在 Flutter 中使用 Platform View:
首先,需要在 Android 的 MainActivity 或 FlutterActivity 中注册 PlatformViewFactory:
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.PluginRegistry
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine
.platformViewsController
.registry
.registerViewFactory("native_text_view", NativeTextViewFactory(flutterEngine.dartExecutor.binaryMessenger))
}
}
代码解释:
- 在
configureFlutterEngine方法中,我们获取PlatformViewsController的registry,并调用registerViewFactory方法来注册NativeTextViewFactory。 registerViewFactory方法接收两个参数:viewType和PlatformViewFactory。viewType是一个字符串,用于标识 Platform View 的类型。在这里,我们将其设置为 "native_text_view"。
接下来,在 Flutter 代码中使用 AndroidView Widget 来显示原生 TextView:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class NativeTextView extends StatelessWidget {
const NativeTextView({Key? key, required this.text}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 200,
height: 100,
child: AndroidView(
viewType: 'native_text_view',
creationParams: <String, dynamic>{'text': text},
creationParamsCodec: const StandardMessageCodec(),
onPlatformViewCreated: (id) {
print('Platform view created with id: $id');
},
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Platform View Example'),
),
body: Center(
child: NativeTextView(text: 'Text from Flutter!'),
),
);
}
}
代码解释:
- 我们使用
AndroidViewWidget 来显示原生 TextView。 viewType属性必须与在 Android 代码中注册的viewType相同,即 "native_text_view"。creationParams属性用于传递参数给原生视图。在这里,我们传递了一个text参数,用于设置 TextView 的文本内容。creationParamsCodec属性用于指定参数的编解码器。在这里,我们使用StandardMessageCodec。onPlatformViewCreated是一个回调函数,在 Platform View 创建成功后被调用。
运行程序后,你将在 Flutter 应用中看到一个显示原生 Android TextView 的 Platform View,并且 TextView 的文本内容是从 Flutter 传递过来的。
实现 PlatformViewFactory:iOS 示例
与 Android 类似,我们也可以在 iOS 上实现自定义的 PlatformViewFactory。
import Flutter
import UIKit
class NativeTextViewFactory: NSObject, FlutterPlatformViewFactory {
private var messenger: FlutterBinaryMessenger
init(messenger: FlutterBinaryMessenger) {
self.messenger = messenger
super.init()
}
func create(
withFrame frame: CGRect,
viewIdentifier viewId: Int64,
arguments args: Any?
) -> FlutterPlatformView {
return NativeTextView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
}
public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
return FlutterStandardMessageCodec.sharedInstance()
}
}
class NativeTextView: NSObject, FlutterPlatformView {
private var _view: UITextView
init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger messenger: FlutterBinaryMessenger?) {
_view = UITextView()
super.init()
// iOS views can be created here
_view.frame = frame
_view.text = "Hello from Native TextView!"
_view.backgroundColor = UIColor.white
if let args = args as? [String: Any] {
if let text = args["text"] as? String {
_view.text = text
}
}
}
func view() -> UIView {
return _view
}
}
代码解释:
NativeTextViewFactory类实现了FlutterPlatformViewFactory协议,并实现了create方法。create方法接收一个CGRect对象、一个viewId和一个args对象。CGRect是 Platform View 的大小和位置,viewId是 Platform View 的唯一标识符,args是从 Flutter 传递过来的参数。- 在
create方法中,我们创建了一个NativeTextView实例,并将CGRect、viewId、args和messenger传递给它。 NativeTextView类实现了FlutterPlatformView协议,并持有一个UITextView对象。- 在
NativeTextView的构造函数中,我们创建了一个UITextView实例,并对其进行了一些初始化操作,例如设置文本内容和背景颜色。 我们还从args参数中获取文本内容,并将其设置到UITextView中。 view方法返回UITextView对象,Flutter 会将这个UIView添加到 Flutter 的 Widget 树中。
原生视图的生命周期桥接:
在 iOS 中,FlutterPlatformView 协议没有直接提供像Android的 dispose 方法那样的生命周期回调。 因此,iOS端的资源释放通常需要在原生视图自身的 deinit 方法中进行,或者通过其他方式与Flutter的生命周期同步(例如,使用消息通道发送消息,通知原生视图释放资源)。
在 Flutter 中使用 Platform View:
首先,需要在 iOS 的 AppDelegate.swift 中注册 PlatformViewFactory:
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let nativeTextViewFactory = NativeTextViewFactory(messenger: controller.binaryMessenger)
controller.registrar(forPlugin: "native_text_view")!.register(
nativeTextViewFactory,
withId: "native_text_view")
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
代码解释:
- 在
application:didFinishLaunchingWithOptions:方法中,我们获取FlutterViewController的binaryMessenger,并创建NativeTextViewFactory的实例。 - 然后,我们调用
registrar(forPlugin:).register(withId:)方法来注册NativeTextViewFactory。 withId参数指定 Platform View 的viewType。在这里,我们将其设置为 "native_text_view"。
接下来,在 Flutter 代码中使用 UiKitView Widget 来显示原生 TextView:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'dart:io' show Platform;
class NativeTextView extends StatelessWidget {
const NativeTextView({Key? key, required this.text}) : super(key: key);
final String text;
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return SizedBox(
width: 200,
height: 100,
child: AndroidView(
viewType: 'native_text_view',
creationParams: <String, dynamic>{'text': text},
creationParamsCodec: const StandardMessageCodec(),
onPlatformViewCreated: (id) {
print('Platform view created with id: $id');
},
),
);
} else if (Platform.isIOS) {
return SizedBox(
width: 200,
height: 100,
child: UiKitView(
viewType: 'native_text_view',
creationParams: <String, dynamic>{'text': text},
creationParamsCodec: const StandardMessageCodec(),
onPlatformViewCreated: (id) {
print('Platform view created with id: $id');
},
),
);
} else {
return const Text('Unsupported platform');
}
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Platform View Example'),
),
body: Center(
child: NativeTextView(text: 'Text from Flutter!'),
),
);
}
}
代码解释:
- 我们使用
UiKitViewWidget 来显示原生 TextView。 viewType属性必须与在 iOS 代码中注册的viewType相同,即 "native_text_view"。creationParams属性用于传递参数给原生视图。在这里,我们传递了一个text参数,用于设置 TextView 的文本内容。creationParamsCodec属性用于指定参数的编解码器。在这里,我们使用StandardMessageCodec。onPlatformViewCreated是一个回调函数,在 Platform View 创建成功后被调用。
运行程序后,你将在 Flutter 应用中看到一个显示原生 iOS UITextView 的 Platform View,并且 TextView 的文本内容是从 Flutter 传递过来的。
进一步的生命周期控制
除了在 create 和 dispose 方法中进行生命周期管理外,我们还可以使用 MessageChannel 来实现更精细的控制。
例如,我们可以创建一个 MethodChannel,用于从 Flutter 调用原生视图的方法,例如更新文本内容、设置背景颜色等。 同样,我们也可以创建一个 EventChannel,用于从原生视图向 Flutter 发送事件,例如文本内容改变、点击事件等。
这允许我们在 Flutter 和原生代码之间建立一个双向的通信通道,从而实现更复杂的交互和生命周期管理。
表格:Platform View 生命周期管理策略
| 平台 | 创建 | 销毁 | 其他生命周期事件处理 |
|---|---|---|---|
| Android | NativeTextViewFactory.create():在此方法中创建和初始化 android.view.View。 可以从 PlatformViewCreationParams 中获取参数进行初始化。 |
NativeTextView.dispose():在此方法中释放资源,例如取消注册监听器、释放内存等。 此方法在 Flutter 销毁 Platform View Widget 时被调用。 |
– 使用 MethodChannel 从 Flutter 调用原生方法。– 使用 EventChannel 从原生视图向 Flutter 发送事件。– 使用 ActivityAware 接口监听 Activity 的生命周期事件 (例如 onAttachedToActivity, onDetachedFromActivity),并根据需要更新原生视图的状态。 |
| iOS | NativeTextViewFactory.create():在此方法中创建和初始化 UIView。 可以从 PlatformViewCreationParams 中获取参数进行初始化。 需要设置视图的 frame。 |
通常在原生视图的 deinit 方法中释放资源。 由于 FlutterPlatformView 协议没有 dispose 方法,需要通过其他方式与 Flutter 的生命周期同步。 |
– 使用 MethodChannel 从 Flutter 调用原生方法。– 使用 EventChannel 从原生视图向 Flutter 发送事件。– 监听 UIViewController 的生命周期事件 (例如 viewWillAppear, viewWillDisappear),可以通过查找 FlutterViewController 的 parentViewController 来实现,但这种做法较为复杂,通常不推荐。– 通过消息通道通知原生视图释放资源,例如在 Flutter 销毁 Platform View Widget 时发送一条消息。 |
优化建议和注意事项
- 谨慎使用 Platform View: Platform View 的性能开销相对较高,因为它需要在 Flutter 和原生代码之间进行上下文切换。因此,只有在必要时才使用 Platform View。
- 避免在 Platform View 中进行复杂的 UI 操作: 尽量将复杂的 UI 操作放在 Flutter 侧进行,以提高性能。
- 合理管理原生视图的生命周期: 确保在视图销毁时释放资源,以避免内存泄漏。
- 注意线程安全: 在原生代码中操作 UI 时,需要确保在主线程上进行。
- 做好错误处理: 在原生代码中处理可能发生的异常,并将其传递给 Flutter 侧。
- 考虑无障碍性 确保原生视图支持无障碍功能,例如屏幕阅读器。
总结:桥接 Flutter 与原生,实现细粒度控制
今天我们深入探讨了如何使用 PlatformViewFactory 来实现 Flutter 和原生视图之间的生命周期桥接。通过自定义 PlatformViewFactory,我们可以在原生视图创建和销毁时执行一些操作,例如初始化视图、释放资源等。 此外,我们还可以使用 MessageChannel 来实现更精细的控制和交互。掌握这些技术,可以帮助我们更好地利用 Platform View,构建更强大的 Flutter 应用。