You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter插件中如何封装同时含视图与方法的原生库类

Flutter插件同时暴露原生视图与类方法的实现方案

完全可以实现,核心思路是把平台视图实例和专属方法通道做绑定,替代全局通道的方案,避免多实例冲突,最终给插件使用者的体验和原生端一致:既可以把组件嵌入渲染,又能拿到实例引用调用公开方法。


核心实现逻辑

  • 放弃全局注册的通用MethodChannel,为每一个嵌入的原生视图实例分配独立通信通道,通道唯一标识和视图id绑定,保证Flutter侧的控制器和原生侧的具体视图实例一一对应。
  • 原生侧创建平台视图实例的同时,直接持有当前视图的引用,和对应id的MethodChannel绑定,通道收到方法调用时直接操作当前持有的视图实例,不需要额外通过findViewById或者属性查找实例。
  • Flutter侧对外暴露的Widget同时承担两个职责:一是作为原生视图的嵌入载体,二是对外暴露和原生实例绑定的方法调用接口,插件使用者拿到配套的控制器即可直接调用对应原生方法。

分端实现要点

Android端

在注册平台视图工厂时,create方法生成自定义原生视图实例后,立刻创建绑定当前viewId的MethodChannel,设置方法调用处理器直接映射到当前视图的公开方法,视图销毁时同步解绑通道避免内存泄漏。

class CustomNativeViewFactory(
    private val messenger: BinaryMessenger
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        // 实例化你的自定义原生视图
        val customView = YourCustomNativeView(context)
        // 绑定当前实例专属的方法通道
        val channel = MethodChannel(messenger, "your_plugin/view_$viewId")
        channel.setMethodCallHandler { call, result ->
            when(call.method) {
                "targetPublicMethod" -> {
                    val params = call.arguments as? Map<String, Any?>
                    val invokeResult = customView.targetPublicMethod(params)
                    result.success(invokeResult)
                }
                // 其余公开方法按相同逻辑映射即可
                else -> result.notImplemented()
            }
        }
        return object : PlatformView {
            override fun getView() = customView
            override fun dispose() {
                channel.setMethodCallHandler(null)
                customView.release()
            }
        }
    }
}

iOS端

逻辑和Android端完全一致,平台视图工厂创建自定义原生视图实例时,用FlutterBinaryMessenger绑定带唯一视图id的MethodChannel,方法调用处理器直接持有当前视图的引用做方法转发,视图释放时清空handler避免循环引用。

class CustomNativeViewFactory: NSObject, FlutterPlatformViewFactory {
    private let messenger: FlutterBinaryMessenger
    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }
    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -> FlutterPlatformView {
        return CustomNativeView(
            frame: frame,
            viewId: viewId,
            args: args,
            messenger: messenger
        )
    }
}

class CustomNativeView: NSObject, FlutterPlatformView {
    private let nativeView: YourCustomNativeView
    private let channel: FlutterMethodChannel
    init(frame: CGRect, viewId: Int64, args: Any?, messenger: FlutterBinaryMessenger) {
        nativeView = YourCustomNativeView(frame: frame)
        channel = FlutterMethodChannel(
            name: "your_plugin/view_\(viewId)",
            binaryMessenger: messenger
        )
        super.init()
        channel.setMethodCallHandler { [weak self] call, result in
            guard let self = self else { return }
            switch call.method {
            case "targetPublicMethod":
                let invokeResult = self.nativeView.targetPublicMethod()
                result(invokeResult)
            default:
                result(FlutterMethodNotImplemented)
            }
        }
    }
    func view() -> UIView {
        return nativeView
    }
    deinit {
        channel.setMethodCallHandler(nil)
    }
}

Flutter侧封装

对外暴露StatefulWidget嵌入对应平台的原生视图,视图创建完成后用拿到的viewId初始化专属MethodChannel,配套控制器类封装所有和原生对应的公开方法,插件使用者初始化控制器后,既能把Widget放到视图树渲染,又能通过控制器直接调用原生方法。

class CustomNativeWidget extends StatefulWidget {
  final CustomNativeController controller;
  const CustomNativeWidget({super.key, required this.controller});
  @override
  State<CustomNativeWidget> createState() => _CustomNativeWidgetState();
}

class _CustomNativeWidgetState extends State<CustomNativeWidget> {
  late MethodChannel _channel;
  @override
  Widget build(BuildContext context) {
    if (Platform.isAndroid) {
      return AndroidView(
        viewType: "your_plugin/custom_native_view",
        onPlatformViewCreated: (viewId) {
          _channel = MethodChannel("your_plugin/view_$viewId");
          widget.controller.attachChannel(_channel);
        },
      );
    } else if (Platform.isIOS) {
      return UiKitView(
        viewType: "your_plugin/custom_native_view",
        onPlatformViewCreated: (viewId) {
          _channel = MethodChannel("your_plugin/view_$viewId");
          widget.controller.attachChannel(_channel);
        },
      );
    }
    throw UnsupportedError("当前平台暂不支持");
  }
}

class CustomNativeController {
  MethodChannel? _channel;
  /// 内部方法,用于视图创建后绑定通道
  void attachChannel(MethodChannel channel) {
    _channel = channel;
  }
  /// 对外暴露的公开方法,和原生类方法一一对应
  Future<dynamic> targetPublicMethod(Map<String, dynamic> params) async {
    assert(_channel != null, "视图未初始化完成,暂不能调用方法");
    return _channel!.invokeMethod("targetPublicMethod", params);
  }
}

注意事项

  • 禁止用全局静态MethodChannel处理视图相关的方法调用,否则页面同时存在多个原生视图实例时,会出现方法调用错配的问题。
  • 所有对外暴露的方法需要加空判断,在视图未创建完成/已经销毁时拦截调用,避免抛出空异常。
  • 如果原生视图有主动通知Flutter的事件需求,直接复用当前实例绑定的MethodChannel发送即可,不需要额外注册事件通道。

内容的提问来源于stack exchange,提问作者Wei Wen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 17:15:41