Flutter custom plug-in basics

1. What is a Flutter plug-in? Official plug-in library

The process of developing Flutter applications will involve platform-related interface calls, such as database operations, camera calls, external browser jumps and other business scenarios. In fact, Flutter itself does not support implementing these functions directly on the platform. Instead, it calls the specified platform API through the plug-in package interface to implement specific functions on the native platform.

2. Directory structure of Flutter plug-in

  • lib is the entrance to the Dart side code. After receiving the parameters from this file, the data is sent to the native side through the channel.
  • android Android code implementation directory
  • ios ios native implementation directory
  • example a Flutter app that depends on the plugin to illustrate how to use it
  • README.md: File introducing the package
  • CHANGELOG.md records changes in each version
  • LICENSE A file containing the license terms for a software package

3. How to create Flutter plug-in package

3.1 Create using command line

flutter create --template=package hello

You can specify the package identifier through –org

flutter create --template=package hello

Specify the language type used by ios and Android code through parameters

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

3.2 使用AS直接new工程

4. Flutter plug-in function writing

After the flutter plug-in template is generated, an external entrance dart class will be automatically generated in the lib folder. All functions included in the plug-in use this class as the entrance to provide external calls. Take a plug-in named hello as an example

platformVersion is an external method call, but the implementation logic inside the method is obtained through the native end. The entry file corresponding to the android native side is as follows

To listen to requests from the dart side, you need to inherit the MethodChannel.MethodCallHandler interface, and then process and return the data logic to the dart side in the onMethodCall method callback.
result returns the final result to dart. If dart does not need to return the result, it does not need to be called

result.success(Object o)

If you have some simple requirements, you can directly implement them in the plugin here, and finally return the results directly. But for example, if you turn up the camera to take a picture or select an address book contact, you need to open an intent and then get the final result in the OnActivityResult method. How to deal with this situation?

Inherit the PluginRegistry.ActivityResultListener interface

Notice! ! ! > For plug-ins that directly place the source code in the project, the onActivityResult method will not be called during runtime because onActivityResult in MainActivity intercepts the calling action, so the plug-in must be placed in the remote warehouse to receive it normally.

implements PluginRegistry.ActivityResultListener {

....

....

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

}


5. Two registration methods for Flutter plug-ins

5.1 Register through the registerWith method, a very old method in the early days

The registerWith method is loaded through reflection

Currently, plug-ins in old versions of the project are registered using this method, but starting from flutter v1.12.x, it is officially recommended to use the second method to register. The first method will be abolished in future updates, so it will be updated in the future. For a large version of Flutter, it may be necessary to re-modify the registration method of existing plug-ins.

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

If it is a plug-in registered in the old way, use it when getting the activity object.

registrar.activity()
 

5.2 Register through Flutter engine

In Flutter1.12.X version, Embedding-V2API is officially enabled by default on the Android platform, and all official plug-ins have been migrated to the new API. The advantage of Embedding-V2APi is that it provides better support and memory optimization for hybrid development

The registration method of the plug-in is defined in the mainfest.xml file on the Android side of the project, as shown below:

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

In the plugin file of the plug-in, inherit the FlutterPlugin interface and use the following new method to initialize it.


//此处是新的插件加载注册方式
    @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;
    }

If you want to get the activity that the current plug-in is attached to, that is, mainActivity, you need to integrate the ActivityAware interface into the plug-in, and then get it through a callback.

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


6. How to interact between Flutter and native

The interaction model between Flutter and native is similar to a C-S model. Among them, Flutter is the Client layer, and Native is the Server layer. The two communicate through MethodChannel, and the native terminal provides Flutter with existing Native component functions.

On the client side, MethodChannel allows sending messages corresponding to method calls. In terms of platforms, MethodChannel on Android and FlutterMethodChannel on iOS enable receiving method calls and returning results. These classes allow you to develop platform plugins with very little "boilerplate" code.

Flutter and native messaging use standard message codecs, which are relatively efficient binary serialization and deserialization. When receiving and sending messages, these values ​​are automatically serialized and deserialized in the message. For details, please refer to StandardMessageCodec

6.1 What is MethodChannel?

Flutter defines 3 channel models:

  • BasicMessageChannel: used to pass string and semi-structured information
  • MethodChannel: used to pass method invocation (method invocation)
  • EventChannel: used for communication of data streams (event streams)

MethodChannel has a total of 3 member variables

  • String name

There will be multiple Channels in Flutter. A Channel object is uniquely identified by name, so the naming of the Channel must be unique. It is recommended to use the combination of component name_Channel name for naming.

  • BinaryMessenger messenger

BinaryMessenger is a tool for communication between Platform and Flutter. The message format used for communication is binary format data. When we initialize a Channel and register a Handler for processing messages with the Channel, a corresponding BinaryMessageHandler will actually be generated and registered in BinaryMessenger with the channel name as the key. When the Flutter end sends a message to BinaryMessenger, BinaryMessenger will find the corresponding BinaryMessageHandler based on its input parameter channel and hand it over to it for processing.


Binarymessenger is an interface on the Android side, and its specific implementation is FlutterNativeView. On the iOS side, it is a protocol named FlutterBinaryMessenger, and FlutterViewController follows it.


Binarymessenger does not know the existence of Channel, it only deals with BinaryMessageHandler. Channel and BinaryMessageHandler have a one-to-one correspondence. Since the message received by the Channel from the BinaryMessageHandler is binary format data and cannot be used directly, the Channel will decode the binary message into a recognizable message through the Codec (message codec) and pass it to the Handler for processing.


After the Handler has processed the message, it will return the result through the callback function, encode the result into binary format data through the codec, and return it through BinaryMessenger.

  • MethodCodec codec

Message codec Codec is mainly used to convert binary format data into data that can be recognized by Handler
MethodCodec is mainly used to serialize and deserialize the object in MethodCall< /span>
MethodCall is an object generated by Flutter’s call to Native, which contains the method name and a parameter set (map or Json)

6.2 Communication process between Flutter and native

First call from dart layer

_channel.invokeMethod("方法名",参数)
  • The invoke method will encapsulate the incoming method name and parameters into a MethodCall object
  • The MethodCall object is then encoded through MethodCodec to form a binary format.
  • Then send the data in binary format through the send method of BinaryMessenger
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);
    }

In the send method, the dart layer finally calls the native method Window_sendPlatformMessage to send the serialized MethodCall object to the C layer.

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';

We can find the corresponding implementation of the above native method in the native code of the Flutter engine. Here is the key part. You can see that it is finally handed over to the handlePlatformMessage method of WindowClient for implementation.

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

(Here we take Android as an example, the same applies to iOS) You can see that in the Android platform HandlePlatformMessage method, the JNI method is called, and the information received by the c layer is thrown to the java layer.

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);           
	  }
}

Take a look at the java method corresponding to JNI, and finally complete the delivery of dart information through handler.onMessage(). The handler in the method is the MethodHandler we mentioned earlier, and it is also the MethodHandler registered by the Native module of our plug-in. Each MethodHandler has a one-to-one correspondence with the 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);
        }
    }

The onMethodCall method of the plugin-integrated MethodCallHandler interface is called in the handler.onMessage method here:

At the same time, the second parameter Result will be passed in the onMethodCall method. After processing and getting the result data that dart wants, it will be returned through Result.

public interface Result {  
        void success(@Nullable Object var1);
        void error(String var1, @Nullable String var2, @Nullable Object var3);
        void notImplemented();
    }


6.3 When was MethodChannel registered and connected to MethodHandler?

When the plug-in is running, we will call the plug-in's registerWith method. When generating the MethodChannel object, we also register a MethodHandler to the MethodChannel. The MethodHandler object corresponds to the MethodChannel object one-to-one.

7. Type restrictions on data interaction between native and Flutter

8. Plug-in package release
Release process referenceFlutter Chinese website Package release tutorial
 


Flutter writing plug-in flutter_plugin (including Android, iOS) implementation process

As Flutter matures, more and more people use Flutter. As a cross-platform language, its display effect and operation fluency are comparable to native ones, which is why Flutter is becoming more and more popular.

Although Flutter is becoming more and more powerful, there are always times when it is unable to meet its expectations. The SDKs developed by major manufacturers such as Aurora Push, Maps and other plug-ins do not provide Flutter versions, and the plug-ins we use are also implemented by some developers themselves. For Some unpopular plug-ins do not exist at all, but we have to use them during the development process. At this time we have to write some plug-ins ourselves.

Next we will gradually implement it through iOS, Android, and Flutter.

What I’m talking about here mainly involves the implementation of some plug-ins with operation interfaces. If you use tools, they are almost the same. You can learn them by yourself.

Plug-in introduction

1. Create a plug-in

I don’t check it here. Use java and oc. Whether you choose this depends on your habits. I don’t recommend checking it here. 

 After clicking Finish, the plug-in is created.

2. Plug-in directory

Android is where we develop the Android part

iOS is where we develop iOS

lib is the location for joint debugging with Android and iOS. It can also be understood as the location of Flutter implementation

example is the location of the test. When we finish writing the plug-in, we can run the plug-in directly. Example can be understood as a Flutter project, but this project only provides plug-in services for you. 

At this point, the plug-in is introduced, let’s start with the code implementation.

Flutter part

1. Add native and Flutter interaction channels

We open the plug-in and find the lib. There will be a file FlutterPluginTest_1 under the lib. On this basis, we will expand it to be more flexible.

 
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;
    }
  }
 
}


In this way, the interaction between native and Flutter is realized. You may have questions. There is no UI interface. Next, I will explain the UI interface to you.

2. Flutter interface explanation

We create a new class called TestView, and the position is parallel to 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));
  }
}

At this point, the Flutter part is accepted, and the next step is to use it.

3. Flutter call

As introduced above, example is a place for testing. I will use it here. We find main.dart and call it.

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 调用原生成功了');
  }
 
}

That’s it for the Flutter part. Let’s introduce the iOS and Android parts. The iOS and Android parts are similar.

Introduction to iOS and Android

iOS part

iOS Find the ios directory and select Reveal in Finder. Because there is no pod install in this ios part now, we need to perform pod install first. After success, just open the project directly. The effect is as follows: 

 Here we find FlutterPluginTest_1Plugin. This class is deeply hidden. It is the core of Flutter's interaction with native. Here we can receive Flutter's content. This part of Android is the same as iOS, there is no difference at all

Android part

For Android, we also right-click to open it in the tool, and then find the location as shown below. All Android code is done here.

Here we find FlutterPluginTest_1Plugin. This class is deeply hidden. It is the core of Flutter's interaction with native. Here we can receive Flutter's content.

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 (because it is consistent with the iOS code, I won’t introduce it too much here)
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

This iOS and Android class can be understood as converting Flutter content into native content that can be used

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

Here TestFlutterPluginView is the native interface to be drawn. We need to draw our UI interface here, using the size passed by 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() {
 
    }
}


At this point, the development of the plug-in is complete. The effect achieved is as follows

The following is how to use this plug-in and integrate it into other projects. Here we only introduce local use.

In fact, local use is very simple.

1. Open our project

2. Open pubspec.yaml

3. Introduce dependencies

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

 flutter_plugin_test_1 The name of the plug-in is the file name when we create the plug-in. Path is the path. We find the plug-in location and paste the path here.

4、pub get

This completes the citation.

5. Just use it exactly the same as in the example.

At this point we have completed the creation and use of the plug-in. If you have anything, you can leave a message in the comment area at any time.

Guess you like

Origin blog.csdn.net/jdsjlzx/article/details/134954317