Flutter 自定义插件基础_flutter 创建插件-程序员宅基地

技术标签: flutter  

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

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

2、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工程

4、Flutter插件功能编写

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

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

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

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

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 方法:

同时在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对象是一一对应的。

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

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


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

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

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

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

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

插件介绍

1、创建插件

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

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

2、插件目录

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,成功后直接打开项目即可,效果如下 

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

Android 部分

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

在这里我们找到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 里面一摸一样就可以了。

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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jdsjlzx/article/details/134954317

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf