自定义 Platform View:实现 `PlatformViewFactory` 与原生视图的生命周期桥接

自定义 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 可以创建 MethodChannelEventChannel,用于 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 实例,并将 ContextviewIdmessengerargs 传递给它。
  • 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 的 MainActivityFlutterActivity 中注册 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 方法中,我们获取 PlatformViewsControllerregistry,并调用 registerViewFactory 方法来注册 NativeTextViewFactory
  • registerViewFactory 方法接收两个参数:viewTypePlatformViewFactoryviewType 是一个字符串,用于标识 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!'),
      ),
    );
  }
}

代码解释:

  • 我们使用 AndroidView Widget 来显示原生 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 实例,并将 CGRectviewIdargsmessenger 传递给它。
  • 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: 方法中,我们获取 FlutterViewControllerbinaryMessenger,并创建 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!'),
      ),
    );
  }
}

代码解释:

  • 我们使用 UiKitView Widget 来显示原生 TextView。
  • viewType 属性必须与在 iOS 代码中注册的 viewType 相同,即 "native_text_view"。
  • creationParams 属性用于传递参数给原生视图。在这里,我们传递了一个 text 参数,用于设置 TextView 的文本内容。
  • creationParamsCodec 属性用于指定参数的编解码器。在这里,我们使用 StandardMessageCodec
  • onPlatformViewCreated 是一个回调函数,在 Platform View 创建成功后被调用。

运行程序后,你将在 Flutter 应用中看到一个显示原生 iOS UITextView 的 Platform View,并且 TextView 的文本内容是从 Flutter 传递过来的。

进一步的生命周期控制

除了在 createdispose 方法中进行生命周期管理外,我们还可以使用 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 应用。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注