1、Flutter插件是什么?官方插件库

在开发Flutter应用过程中会涉及到平台相关接口调用,例如数据库操作、相机调用、外部浏览器跳转等业务场景。其实Flutter自身并不支持直接在平台上实现这些功能,而是通过插件包接口去调用指定平台API从而实现原生平台上特定功能。

2、Flutter插件的目录结构

android flutter 代码提示插件_flutter

  • lib 是对接dart端代码的入口,由此文件接收到参数后,通过chennel将数据发送到原生端
  • android 安卓端代码实现目录
  • ios ios原生端实现目录
  • example 一个依赖于该插件的Flutter应用程序,来说明如何使用它
  • README.md:介绍包的文件
  • CHANGELOG.md 记录每个版本中的更改
  • LICENSE 包含软件包许可条款的文件

3、Flutter插件包的创建方式

3.1 使用命令行创建

flutter create --template=package hello

可以通过–org指定包标识符

flutter create --template=package hello

通过参数指定ios和Android代码使用的语言类型

flutter create --template=plugin -i swift -a kotlin hello

3.2 使用AS直接new工程

android flutter 代码提示插件_flutter_02

4、Flutter插件功能编写

flutter 插件模板生成后,在lib文件夹下会自动生成一个对外的入口dart类,该插件所包含的所有功能都以此类为入口,来提供外部进行调用。以一个名字为hello的插件为例

android flutter 代码提示插件_iOS_03

platformVersion 是对外的方法调用,但是方法内部的实现逻辑,是通过原生端去获取的。对应android原生端的入口文件如下

android flutter 代码提示插件_iOS_04

监听来自dart端的请求,需要继承MethodChannel.MethodCallHandler接口,然后在onMethodCall方法回调中处理和返回给dart端数据逻辑。
result是给dart端回传最后结果的,如果dart不需要返回结果,也可以不调用

android flutter 代码提示插件_Android_05

result.success(Object o)

如果一些简单的需求,可以直接在此处的plugin里实现,最后将结果直接返回。但是比如调起相机拍照,选取通讯录联系人,这些都要打开一个intent然后在OnActivityResuult方法中去获取最终的结果,这种情况下如何处理呢?

继承 PluginRegistry.ActivityResultListener 接口

注意!!! > 直接将源码放在项目中的插件,在运行时候onActivityResult方法是不会被调用的,因为MainActivity中的onActivityResult将调用动作拦截了下来,所以必须将插件放在远端仓库中才可以正常接收

implements PluginRegistry.ActivityResultListener {

....

....

@Override
  public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
    // 在此处写逻辑,最后拿到结果后通过callback回调到onMethodCall方法内,再回传给dart
    return false;
  }

}

5、Flutter插件的两种注册方式

5.1 通过 registerWith 方式注册,早期非常老旧的方式

registerWith方式是通过反射进行加载

目前老版本项目里的插件都是使用这种方式注册,但是从flutter v1.12.x 开始往后官方推荐使用第二种方式注册,第一种方式会在以后的更新中废除,所以以后更新flutter大版本,可能要重新修改现有插件的注册方式

//此处是旧的插件加载注册方式,静态方法
    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), PLUGIN_NAME);
        channel.setMethodCallHandler(new FlutterXUpdatePlugin().initPlugin(channel, registrar));
    }

如果是旧的方式注册的插件,获取activity对象时候使用

registrar.activity()
 

5.2 通过Flutter引擎注册

在Flutter1.12.X 版本中正式将Embedding-V2API在Android平台默认开启,所有官方插件都迁移到了新的API。Embedding-V2APi的优势在于针对混合开发提供了更好的支持和内存上的优化

插件的注册方式定义在工程的android端的mainfest.xml文件中,如下所示:

//新的注册方式必须指定,旧的方式无需指定此配置
<meta-data
   android:name="flutterEmbedding"
   android:value="2" />

在插件的plugin文件中,继承FlutterPlugin接口,使用以下新的方式进行初始化

//此处是新的插件加载注册方式
    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        mMethodChannel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), PLUGIN_NAME);
        mApplication = (Application) flutterPluginBinding.getApplicationContext();
        mMethodChannel.setMethodCallHandler(this);
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        mMethodChannel.setMethodCallHandler(null);
        mMethodChannel = null;
    }

如需获取当前插件依附的activity,也就是mainActivity,则需要plugin集成ActivityAware接口,然后通过回调获取

@Override
    public void onAttachedToActivity(ActivityPluginBinding binding) {
        mActivity = new WeakReference<>(binding.getActivity());
    }
    
   @Override
    public void onDetachedFromActivity() {
        mActivity = null;
    }

6、Flutter 与原生之间如何交互

Flutter与原生的交互模型,类似于一种C-S模型。其中Flutter为Client层,原生为Server层,两者通过MethodChannel进行消息通信,原生端向Flutter提供已有的Native组件功能。

在客户端,MethodChannel允许发送与方法调用相对应的消息。 在平台方面,Android上的MethodChannel和iOS上的FlutterMethodChannel启用接收方法调用并返回结果。 这些类允许你使用非常少的“样板”代码开发平台插件。


Flutter与原生的消息传递采用标准信息编解码器,是一种相对高效的二进制序列化与反序列化。当接收跟发送消息时,这些值在消息中会自动进行序列化与反序列化。详细的请参阅StandardMessageCodec

android flutter 代码提示插件_Android_06

6.1 什么是MethodChannel?

Flutter定义了3种channel模型:

  • BasicMessageChannel:用于传递字符串和半结构化的信息
  • MethodChannel:用于传递方法调用(method invocation)
  • EventChannel: 用于数据流(event streams)的通信

MethodChannel总共有3个成员变量

  • String name

在Flutter中会存在多个Channel,一个Channel对象通过name来进行唯一的标识,所以在Channel的命名上一定要独一无二,推荐采用组件名_Channel名 组合来进行命名

  • BinaryMessenger messenger

BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。

Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。

Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。

当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger返回。

  • MethodCodec codec

消息编解码器Codec主要用于将二进制格式的数据转化为Handler能够识别的数据
MethodCodec主要是对MethodCall中这个对象进行序列化与反序列化
MethodCall是Flutter向Native发起调用产生的对象,其中包含了方法名以及一个参数集合(map或者是Json)

6.2 Flutter 与原生之间的通信流程

首先从dart层调用

_channel.invokeMethod("方法名",参数)
  • invoke方法会将传入的方法名与参数封装成MethodCall对象
  • 然后通过MethodCodec对MethodCall对象进行编码,形成二进制格式。
  • 然后通过BinaryMessenger的send方法,将二进制格式的数据进行发送
Future<dynamic> invokeMethod(String method, [dynamic arguments]) async {  
        assert(method != null);
        ///发送 messenge
        final dynamic result = await BinaryMessages.send(
          name,
          codec.encodeMethodCall(MethodCall(method, arguments)),
        );
        if (result == null)
          throw MissingPluginException('No implementation found for method $method on channel $name');
        return codec.decodeEnvelope(result);
    }

send方法里,dart层最终调用native方法 Window_sendPlatformMessage ,将序列化后的MethodCall对象向 C 层发送

static Future<ByteData> send(String channel, ByteData message) {  
    final _MessageHandler handler = _mockHandlers[channel];
    if (handler != null) 
      return handler(message);
    return _sendPlatformMessage(channel, message);
}

String _sendPlatformMessage(String name,  
   PlatformMessageResponseCallback callback,
   ByteData data) native 'Window_sendPlatformMessage';

我们在Flutter engine的native代码中可以找到上述native方法的对应实现,这里截取关键部分,可以看到最后是交给了WindowClient的handlePlatformMessage方法进行实现

dart_state->window()->client()->HandlePlatformMessage(  
        fml::MakeRefCounted<PlatformMessage>(name, response));

(这里以Android举例,iOS同理)可以看到,在Android平台HandlePlatformMessage方法中,调用到了JNI方法,将c层收到的信息向java层抛

void PlatformViewAndroid::HandlePlatformMessage(  
    fml::RefPtr<blink::PlatformMessage> message) 
    {
	  JNIEnv* env = fml::jni::AttachCurrentThread();
	  fml::jni::ScopedJavaLocalRef<jobject> view = java_object_.get(env);
	  auto java_channel = fml::jni::StringToJavaString(env, message->channel()); 
	  if (message->hasData()) {
	    fml::jni::ScopedJavaLocalRef<jbyteArray> message_array(env, env->NewByteArray(message->data().size()));
	    env->SetByteArrayRegion(
	        message_array.obj(), 0, message->data().size(),
	        reinterpret_cast<const jbyte*>(message->data().data()));
	    message = nullptr;
	    // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
	    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
	                                     message_array.obj(), response_id);  
	  } else {
	    message = nullptr;
	    // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
	    FlutterViewHandlePlatformMessage(env, view.obj(), java_channel.obj(),
	                                     nullptr, response_id);           
	  }
}

看一下JNI对应的java方法,最终通过handler.onMessage(),完成了本次dart信息的传递。方法中的handler,就是我们前面提到的MethodHandler,也是我们插件的Native模块注册的MethodHandler,每一个MethodHandler 都和 MethodChannel是一一对应的关系

private void handlePlatformMessage(final String channel, byte[] message, final int replyId) {
        this.assertAttached();
        BinaryMessageHandler handler = (BinaryMessageHandler)this.mMessageHandlers.get(channel); 
        if (handler != null) {
            try {
                ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
                handler.onMessage(buffer, new BinaryReply() {
                    // ...
                });
            } catch (Exception var6) {
                // ...
            }
        } else {
            Log.e("FlutterNativeView", "Uncaught exception in binary message listener", var6);
            nativeInvokePlatformMessageEmptyResponseCallback(this.mNativePlatformView, replyId);
        }
    }

此处的handler.onMessage方法内调用了plugin集成的MethodCallHandler接口的 onMethodCall 方法:

android flutter 代码提示插件_Android_07

同时在onMethodCall方法中会传入第二个参数 Result ,当处理完拿到dart想要的结果数据后,通过Result来进行回传。

public interface Result {  
        void success(@Nullable Object var1);
        void error(String var1, @Nullable String var2, @Nullable Object var3);
        void notImplemented();
    }
6.3 MethodChannel是什么时候注册,和MethodHandler联系起来的呢?

在插件运行的时候,我们会调用插件的registerWith方法,在生成MethodChannel对象时,同时向MethodChannel注册了一个MethodHandler,MethodHandler对象跟MethodChannel对象是一一对应的。

android flutter 代码提示插件_flutter_08

7、原生和Flutter之间数据交互的类型限制

android flutter 代码提示插件_flutter_09

8、插件包的发布
发布过程参考Flutter中文网Package发布教程  


Flutter 编写插件flutter_plugin(包含Android、iOS)实现过程

随着Flutter 日渐成熟,使用Flutter 也越来越多,作为一个跨平台的语言,他的展示效果和操作流畅度 可以和原生媲美,这也Flutter 越来越受欢迎的原因。

虽然Flutter 越来越强大,但是总有一些力不从心的时候,现在大厂开发的SDK 如 极光推送、地图 等插件 并没有提供 Flutter 版本,而我们使用的插件也是 一些开发者自己进行实现的,而对于一些冷门的插件,是根本没有,但是在开发过程中我们又要使用到。这个时候我们就要自己写一些插件了。

下面我们就要通过iOS、Android 、Flutter 逐步实现。

这里我讲的主要涉及到一些带有操作界面的插件实现,如果是使用工具其实 也差不多,大家可自行学习一下。

插件介绍

1、创建插件

android flutter 代码提示插件_flutter_10

这里我是不勾选的,使用java、oc,这个是否选择看大家习惯,这里我是不建议勾选的 

android flutter 代码提示插件_iOS_11

 点击完成后,插件就创建完成了。

2、插件目录

android flutter 代码提示插件_flutter_12

Android 就是我们开发安卓部分的位置

iOS 就是我们开发 iOS 的位置

lib 是与 Android 、iOS 联调的位置。也可以理解为Flutter 实现的位置

example 是测试的位置,当我们写完插件 可以直接运行 插件,example 可以理解为一个Flutter项目,只不过这个项目只给你写的插件服务 

到此 插件就介绍了完了,下面开始进行代码实现。

Flutter部分

1、添加原生、Flutter交互渠道

我们打开插件,找到lib ,在lib下面会有一个文件 FlutterPluginTest_1,在这个基础上我们进行扩展,更加灵活

import 'dart:async';
 
import 'package:flutter/services.dart';
///先定义一个controller创建成功回调 后面会用到,这里如果 我们把一个视图创建成功后,会将这的对象返回,拿到这个对象,我们可以通过这个对象的内渠道 方法进行控制等操作
typedef void TestViewCreatedCallback(FlutterPluginTest_1 controller);
class FlutterPluginTest_1 {
 
  // 原生与Flutter 交互渠道
  MethodChannel _channel;
  // 重写 构造方法,通过 id 创建不同的渠道,灵活度更高
  FlutterPluginTest_1.init(int id){
    /// 初始化 渠道
    _channel = new MethodChannel('FlutterPluginTest_1'); 
    ///设置原生参数监听
    _channel.setMethodCallHandler(platformCallHandler);
  }
 
  /// Flutter 调用原生
  /// 这里我传了一个 字符串 当然也可以传Map,
  /// 'changeNativeTitle' 是一个方法名,因为一个渠道可以有多个 方法,我们可以根据一个方法名进行对应的操作
  Future<void> changeNativeTitle(String str) async{
    return _channel.invokeListMethod('changeNativeTitle',str);
  }  
 
  ///实现监听原生方法回调
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "clickAciton":
        print('收到原生回调 ---- $call.arguments');
        return ;
        break;
    }
  }
 
}

这样就实现了 原生与Flutter之间的交互,大家可能会有疑问,这也没有UI界面啊。下面就给大家讲解UI界面

2、Flutter界面讲解

我们创建一个新类,叫TestView,位置和FlutterPluginTest_1并列即可。

import 'dart:io';
 
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
 
import 'flutter_plugin_test_1.dart';
 
///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
  ///根据自己的需求创建初始化参数
  final TestViewCreatedCallback onCreated; /// 我们就是用到是FlutterPluginTest_1上面创建的回调
  final String titleStr;
 
  TestView({
    Key key,
    this.onCreated,
    this.titleStr,
  });
 
  @override
  _TestViewState createState() => _TestViewState();
}
 
class _TestViewState extends State<TestView> {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _loadNativeView(),
    );
  }
  ///加载原生视图
  Widget _loadNativeView(){
    ///根据不同的平台显示相应的视图
    if(Platform.isAndroid){ ///加载安卓原生视图
      return AndroidView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else if(Platform.isIOS){///加载iOS原生视图
      return UiKitView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: <String, dynamic>{ ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else{
      return Text('因为这里只介绍。iOS 、Android,其平台不支持');
    }
  }
  ///这个基本上是固定写法
  Future<void> onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
    }
    widget.onCreated(new FlutterPluginTest_1.init(id));
  }
}

到这里,Flutter 部分就算接受完事了,下面就是使用了

3、Flutter调用

上面介绍到example是测试的地方,下面我就在这里进行使用,我们找main.dart,然后调用。

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_plugin_test_1/flutter_plugin_test_1.dart';
import 'package:flutter_plugin_test_1/TestView.dart';
void main() {
  runApp(MyApp());
}
 
class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}
 
class _MyAppState extends State<MyApp> {
  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo; // 定一个插件的FlutterPluginTest_1对象,
  @override
  void initState() {
    super.initState();
  }
 
 
  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类,我们写的TextView
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text('Plugin example app'),
          ),
          body: Column(
            children: <Widget>[
              Container(
                height: 200,
                width: 400,
                child: testView,///使用原生视图
              ),
              FloatingActionButton( ///添加一个按钮 用来触发原生调用
                onPressed: onNativeMethon, ///点击方法里面调用原生
              )
            ],
          )
      ),
    );
  }
  
  /// FlutterPluginTest_1 中的callBack,当创建UI创建成功,会后到FlutterPluginTest_1的对象会掉,并赋值给testFlutterPluginDemo
  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }
  /// 调用原生
  void onNativeMethon(){
    this.testFlutterPluginDemo.changeNativeTitle('Flutter 调用原生成功了');
  }
 
}

Flutter 部分就完事了,下面介绍iOS、Android 部分,iOS 和 Android 部分类似

iOS、Android介绍

iOS部分

iOS 找到 ios 目录,选择Reveal in Finder,因为现在这个ios 部分还没有pod install,我们这要先进行pod install,成功后直接打开项目即可,效果如下 

android flutter 代码提示插件_iOS_13

 在这里我们找到FlutterPluginTest_1Plugin,这个类隐藏的很深,他是Flutter 与原生交互的核心,在这了我们可以接收到Flutter的内容。Android 这部分和iOS 是同一个道理,没有丝毫区别

Android 部分

Android 我们也右键在工具中打开,然后如下图找到位置,Android 所有的代码都在这里进行

android flutter 代码提示插件_iOS_14

在这里我们找到FlutterPluginTest_1Plugin,这个类隐藏的很深,他是Flutter 与原生交互的核心,在这了我们可以接收到Flutter的内容。

iOS FlutterPluginTest_1Plugin
#import "FlutterPluginTest_1Plugin.h"
#import "TestFlutterPluginViewFactory.h"
@implementation FlutterPluginTest_1Plugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
    // 这里是对原生部分的界面与Flutter的一个关联
    TestFlutterPluginViewFactory *testViewFactory = [[TestFlutterPluginViewFactory alloc] initWithMessenger:registrar.messenger];
    //这里填写的id 一定要和dart里面的viewType 这个参数传的id一致
    [registrar registerViewFactory:testViewFactory withId:@"testView"];
}
 
@end
Android FlutterPluginTest_1Plugin(由于和iOS代码一致,这里不做过多介绍)
package com.dhc.abox.flutter_plugin_test_1;
 
import android.content.Context;
import android.widget.Toast;
 
import androidx.annotation.NonNull;
 
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
 
/** FlutterPluginTest_1Plugin */
public class FlutterPluginTest_1Plugin implements FlutterPlugin {
 
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    flutterPluginBinding.getPlatformViewRegistry().registerViewFactory("testView", new TestFlutterPluginViewFactory(flutterPluginBinding.getBinaryMessenger()));
  }
 
  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
  }
 
  /**
   * 旧版插件加载
   *
   * @param registrar
   */
  public static void registerWith(Registrar registrar) {
    //播放器注册
    registrar.platformViewRegistry().registerViewFactory("testView", new TestFlutterPluginViewFactory(registrar.messenger()));
  }
 
}
iOS TestFlutterPluginViewFactory

这个类iOS和Android 可以理解为将 Flutter的内容转成原生可以使用的内容

iOS .h

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
 
@interface TestFlutterPluginViewFactory : NSObject<FlutterPlatformViewFactory>
 
/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;
 
@end
NS_ASSUME_NONNULL_END

.m 

//
//  TestFlutterPluginViewFactory.m
//  flutter_plugin_test_1
//
//  Created by sunyd on 2022/1/24.
//
 
#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()
 
@property(nonatomic)NSObject<FlutterBinaryMessenger>* messenger;
 
@end
 
@implementation TestFlutterPluginViewFactory
 
- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messenger {
    self = [super init];
    if (self) {
        self.messenger = messenger;
    }
    return self;
}
 
#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject<FlutterMessageCodec>*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}
 
/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
    
    TestFlutterPluginView *testFlutterPluginView = [[TestFlutterPluginView alloc] initWithFrame:frame viewId:viewId args:args messager:self.messenger];
    return testFlutterPluginView;
    
}
 
@end
Android TestFlutterPluginViewFactory
package com.dhc.abox.flutter_plugin_test_1;
 
import android.content.Context;
 
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MessageCodec;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
 
/**
 * Created by sunyd on 1/25/22
 */
public class TestFlutterPluginViewFactory extends PlatformViewFactory {
    private BinaryMessenger messenger = null;
    public TestFlutterPluginViewFactory(BinaryMessenger messenger) {
        super(StandardMessageCodec.INSTANCE);
        this.messenger = messenger;
    }
 
    /**
     * @param createArgsCodec the codec used to decode the args parameter of {@link #create}.
     */
    public TestFlutterPluginViewFactory(MessageCodec<Object> createArgsCodec) {
        super(createArgsCodec);
    }
 
    @Override
    public PlatformView create(Context context, int viewId, Object args) {
        return new TestFlutterPluginView(context, viewId, args, this.messenger);
    }
}
iOS TestFlutterPluginView

这里TestFlutterPluginView 就是原生要绘制的界面,我们要在这里绘制我们的UI界面,通过Flutter传过来的尺寸

.h

#import <Foundation/Foundation.h>
#include <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN
 
@interface TestFlutterPluginView : NSObject<FlutterPlatformView>
/// 固定写法
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject<FlutterBinaryMessenger>*)messenger;
@end
NS_ASSUME_NONNULL_END

.m

//
//  TestFlutterPluginView.m
//  flutter_plugin_test_1
//
//  Created by sunyd on 2022/1/24.
//
 
#import "TestFlutterPluginView.h"
 
@interface TestFlutterPluginView ()
/** channel*/
@property (nonatomic, strong)  FlutterMethodChannel  *channel;
@property (nonatomic, strong)  UIButton  *button;
@end
 
@implementation TestFlutterPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
    
}
 
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject<FlutterBinaryMessenger>*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"FlutterPluginTest_1" binaryMessenger:messenger];
        
        __weak __typeof__(self) weakSelf = self;
        
        [_channel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
            [weakSelf onMethodCall:call result:result];
        }];
        
    }
    return self;
}
 
- (UIView *)view{
    
    UIView *nativeView = [[UIView alloc] initWithFrame:_frame];
    nativeView.backgroundColor = [UIColor redColor];
    _button = [UIButton buttonWithType:UIButtonTypeSystem];
    [_button setTitle:@"我是按钮" forState:UIControlStateNormal];
    [_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
    [_button setBackgroundColor:[UIColor lightGrayColor]];
    _button.frame = CGRectMake(100, 100, 100, 100);
    [nativeView addSubview:_button];
    [_button addTarget:self action:@selector(flutterMethod) forControlEvents:UIControlEventTouchUpInside];
    return nativeView;
    
}
 
#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    //监听Fluter
    if ([[call method] isEqualToString:@"changeNativeTitle"]) {
        UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"提示" message:call.arguments delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
        [alertView show];
    }
    
}
//调用Flutter
- (void)flutterMethod{
    [self.channel invokeMethod:@"clickAciton" arguments:@"我是参数"];
}
 
@end
Android TestFlutterPluginView
package com.dhc.abox.flutter_plugin_test_1;
import android.content.Context;
import android.graphics.Color;
import android.graphics.SurfaceTexture;
import android.provider.CalendarContract;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.TextView;
import android.widget.Toast;
 
import androidx.annotation.NonNull;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;
 
/**
 * Created by sunyd on 1/25/22
 */
public class TestFlutterPluginView extends TextView implements PlatformView, MethodChannel.MethodCallHandler, TextureView.SurfaceTextureListener{
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
 
    }
 
    @Override
    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
 
    }
 
    @Override
    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
        return false;
    }
 
    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
 
    }
 
    public  Context context;
    /**
     * 通道
     */
    private  MethodChannel methodChannel = null;
 
    public TestFlutterPluginView(Context context, int viewId, Object args, BinaryMessenger messenger) {
        super(context);
        this.context = context;
        Toast.makeText(context, "创建关联成功", Toast.LENGTH_SHORT).show();
        setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        setBackgroundColor(Color.argb(255,79,79,79));  //0完全透明  255不透明
        //注册
        methodChannel = new MethodChannel(messenger, "FlutterPluginTest_1");
        methodChannel.setMethodCallHandler(this);
    }
 
    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        handleCall(call, result);
    }
    private void handleCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            //开始预览
            case "changeNativeTitle":
                Toast.makeText(context, (String)methodCall.arguments, Toast.LENGTH_SHORT).show();
                break;
            default:
        }
    }
 
    @Override
    public View getView() {
        return this;
    }
 
    @Override
    public void dispose() {
 
    }
}

到此,插件的开发就算是完事了。实现的效果如下

下面就是使用这个插件了,我们如何集成到 别的项目里,在这里 我们只介绍 本地 使用

其实本地使用非常简单。

1、打开我们的项目

2、打开pubspec.yaml

3、引入依赖

dependencies:
   flutter:
     sdk: flutter
  
   flutter_plugin_test_1:
     path: /Users/sunyd/Desktop/flutter_plugin_test_1

 flutter_plugin_test_1 位插件的名称,就是我们创建插件时候的文件名称,path就是路径了,我们找到插件位置 将路径 粘贴到这里即可

4、pub get

到此就引用完成了。

5、使用我们就和example 里面一摸一样就可以了。

到此我们就完成了插件的 创建 和使用,大家有什么可以随时评论区留言。