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 里面一摸一样就可以了。
到此我们就完成了插件的 创建 和使用,大家有什么可以随时评论区留言。