前言

做了好多年的iOS开发,虽然问题遇到不少,也解决过不少,但是都没有记录下来,因为自己懒,有时候也因为自己工作忙忘记了,没有写过也没有维护过iOS相关的技术文章,没想着这里都深感惭愧,
Flutter 从1.0发布到现在也挺长时间了,也有许多公司已经开始尝试做吃螃蟹的人,刚好公司现在用到Flutter开发,但是Flutter 插件并不像原生iOS或者原生安卓那么丰富,有时候PM的需求又是那么的难搞,难免避免不了需要自己开发插件,那我就把我开发插件的小经验分享出来,希望能帮助到一些朋友.

一 准备工作

Flutter 环境搭建我就不在这里赘述了,新建一个组件模板,有两种新建方式一种是通过命令行,一种是通过Android Studio

通过命令行创建:
flutter create -t plugin flutter_plugin
Android Studio 创建

选择第二项 Start a new Flutter project

frc 6f0251279038171e7c6f3613f897c369 - Flutter开发之Flutter插件开发一

选择Flutter Plugin

frc 67ba23dc200e7fea39fb10668fac2327 - Flutter开发之Flutter插件开发一
安卓第二步骤.jpg

填写插件名称

frc 49728248f68b60f5fcacff6602c5f24e - Flutter开发之Flutter插件开发一
3.jpg

红线圈起来的地方根据自己的语言选择 默认安卓项目是java iOS项目是Object-c,要是需要Kotlin 和 Swift的话 要打上勾,我是一直使用Object-c 所以下面的讲解演示用OC 来写

frc c52dca6ff584d3121bda7793004cfddb - Flutter开发之Flutter插件开发一
4.jpg

这是新创建完的插件模板目录

frc 209af4ca702909b0b4077e8c5c1bc366 - Flutter开发之Flutter插件开发一
5.jpg

写原生代码的话 就在example 目录里找到对应的iOS 或者 是安卓目录,以iOS为例 新创建的iOS代码是不包含 pod文件夹的所以无法运行 需要先在example里面的iOS 文件夹里运行 pod install 来安装需要的依赖包

frc 7f002241c7fad946822e18c10fe9861e - Flutter开发之Flutter插件开发一
6.jpg

然后就可以运行示例工程了

frc 453b4e87b6b79963d5dfb1e9c18e138f - Flutter开发之Flutter插件开发一
7.jpg

至此Flutter 插件开发准备工作就结束了,下面让我们进入真正的插件开发吧

磨拳霍霍向猪羊—- 插件开发

让我们先从Dart代码开始

这是模板刚创建的dart代码

frc e91ffb0f43e3baf08e7fa3fd25ad5e38 - Flutter开发之Flutter插件开发一
8.jpg

我们先简化一下代码把没用的代码删掉 再把相关报错的代码注释掉

///先定义一个controller创建成功回调 后面会用到
typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {
  MethodChannel _channel;
  TestFlutterPluginDemo.init(int id){
    _channel = new MethodChannel('test_flutter_plugin_demo');
  }
}

TestFlutterPluginDemo 这个类 我理解的是 主要负责和原生进行交互,调用原生的方法和监听原生方法,
然后让我们新建一个view类主要是加载原生的视图

///我是使用的 StatefulWidget 使用StatelessWidget 也是一样
class TestView extends StatefulWidget {
  ///根据自己的需求创建初始化参数
  final TestViewCreatedCallback onCreated; ///是上面创建的回调
  final String titleStr;

  TestView({
    Key key,
   this.onCreated,
   this.titleStr,
});

  @override
  _TestViewState createState() => _TestViewState();
}

class _TestViewState extends State {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: _loadNativeView(),
    );
  }
///加载原生视图
  Widget _loadNativeView(){
    ///根据不同的平台显示相应的视图
    if(Platform.isAndroid){ ///加载安卓原生视图
      return AndroidView(
        viewType: 'testView',///视图标识符 要和原生 保持一致 要不然加载不到视图
        onPlatformViewCreated:onPlatformViewCreated,///原生视图创建成功的回调
        creationParams: { ///给原生传递初始化参数 就是上面定义的初始化参数
          '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: { ///给原生传递初始化参数 就是上面定义的初始化参数
          'titleStr':widget.titleStr,
        },
        /// 用来编码 creationParams 的形式,可选 [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]
        /// 如果存在 creationParams,则该值不能为null
        creationParamsCodec: const StandardMessageCodec(),
      );
    }else{
      return Text('这个平台老子不支持');
    }
  }
  ///我也是刚学Flutter 所以我理解的:这个基本上是固定写法   哈哈
  Future onPlatformViewCreated(id) async {
    if (widget.onCreated == null) {
      return;
    }
    widget.onCreated(new TestFlutterPluginDemo.init(id));
  }
}

截止到现在Dart的主要代码就写完了,代码中的注释已经很细了,小伙伴们可以跟着代码一步一步的敲一下,增强一下记忆力和代码熟悉程度.
剩下的就是在main.dart 里面加载视图了,

import 'package:flutter/material.dart';
import 'package:test_flutter_plugin_demo/test_flutter_plugin_demo.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {
 
  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo;

  @override
  void initState() {
    super.initState();

  }
  
  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );
    
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          children: [
            Container(
              height: 200,
              width: 400,
              child: testView,///使用原生视图
            )
          ],
        )
      ),
    );
  }

  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }
}

在main.dart里面添加加载视图的代码后 插件开发 Dart相关代码就写完了,下面咱们开始写原生代码了,强调一下哈 iOS 代码为例 OC语言

开始原生代码

让我们先看一下测试工程的代码文件目录

frc a93807f7ee25c8daf859885602556f36 - Flutter开发之Flutter插件开发一
9.jpg

发现有两个文件 GeneratedPluginRegistrant.h 和 GeneratedPluginRegistrant.m,但是这两个文件不是咱们需要敲代码的页面,咱们敲代码的页面隐藏的比较深 在下面的这个目录里

frc f6914e01f6f3c65d7b598f727e83680b - Flutter开发之Flutter插件开发一
10.jpg

一定要在pod /Development Pods 里面找到Classes文件夹里的这两个文件 开发, 新建类也要在这个目录里面, 使用过iOS 组件发开发的同学一定很熟悉,原理差不多哟
好了开始原生代码开发吧
先让我们新建一个Factory 来连接 Flutter 的视图吧 新建的 Factory 要继承与 NSObject 然后在新建一个view类也要继承与NSObject 来写原生页面布局 两个类的代码如下

factory.h

#import 
#import 
NS_ASSUME_NONNULL_BEGIN

@interface TestFlutterPluginViewFactory : NSObject

/// 重写一个构造方法 来接收 Flutter 相关蚕食
/// @param messenger Flutter类 包含回调方法等信息
- (instancetype)initWithMessenger:(NSObject*)messenger;

@end

factory.m

#import "TestFlutterPluginViewFactory.h"
#import "TestFlutterPluginView.h"
@interface TestFlutterPluginViewFactory ()

@property(nonatomic)NSObject* messenger;

@end

@implementation TestFlutterPluginViewFactory

- (instancetype)initWithMessenger:(NSObject*)messenger {
    self = [super init];
    if (self) {
        self.messenger = messenger;
    }
    return self;
}

#pragma mark -- 实现FlutterPlatformViewFactory 的代理方法
- (NSObject*)createArgsCodec {
    return [FlutterStandardMessageCodec sharedInstance];
}

/// FlutterPlatformViewFactory 代理方法 返回过去一个类来布局 原生视图
/// @param frame frame
/// @param viewId view的id
/// @param args 初始化的参数
- (NSObject *)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

testView.h

#import 
#include 
NS_ASSUME_NONNULL_BEGIN

@interface TestFlutterPluginView : NSObject
- (id)initWithFrame:(CGRect)frame
             viewId:(int64_t)viewId
               args:(id)args
           messager:(NSObject*)messenger;
@end

testView.m

#import "TestFlutterPluginView.h"

@interface TestFlutterPluginView ()
/** channel*/
@property (nonatomic, strong)  FlutterMethodChannel  *channel;
@end

@implementation TestFlutterPluginView
{
    CGRect _frame;
    int64_t _viewId;
    id _args;
   
}

- (id)initWithFrame:(CGRect)frame
  viewId:(int64_t)viewId
    args:(id)args
messager:(NSObject*)messenger
{
    if (self = [super init])
    {
        _frame = frame;
        _viewId = viewId;
        _args = args;
        
        ///建立通信通道 用来 监听Flutter 的调用和 调用Fluttter 方法 这里的名称要和Flutter 端保持一致
        _channel = [FlutterMethodChannel methodChannelWithName:@"test_flutter_plugin_demo" 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];
    
    return nativeView;
     
}

#pragma mark -- Flutter 交互监听
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    
}

工厂类和视图类都创建完了,现在可以到 刚开始的模板里创建的 TestFlutterPluginDemoPlugin 这个类里去做关联了 是/Development Pods 里面找到Classes文件夹里的这两个文件
删掉自带的无用的代码
连接的实例代码如下
TestFlutterPluginDemoPlugin.h

#import "TestFlutterPluginDemoPlugin.h"
#import "TestFlutterPluginViewFactory.h"

@implementation TestFlutterPluginDemoPlugin
+ (void)registerWithRegistrar:(NSObject*)registrar {
  
    TestFlutterPluginViewFactory *testViewFactory = [[TestFlutterPluginViewFactory alloc] initWithMessenger:registrar.messenger];
//这里填写的id 一定要和dart里面的viewType 这个参数传的id一致
    [registrar registerViewFactory:testViewFactory withId:@"testView"];
    
}

@end

现在显示视图的代码就写完了 想要加载成功还要在 info.plist 里面添加

io.flutter.embedded_views_preview

现在让我们运行一下 看看效果吧

frc 7d67d6ff48d884950af0b15504a0523d - Flutter开发之Flutter插件开发一
11.jpg

加载原生视图到现在就 完成了 下面让我们开始来学习 Flutter 方法互相调用和传值吧

Flutter 和 原生 交互

还是让我们先写Dart 代码 在上面咱们创建的 TestFlutterPluginDemo 这个类里面添加 和原生交互代码

typedef void TestViewCreatedCallback(TestFlutterPluginDemo controller);
class TestFlutterPluginDemo {

  MethodChannel _channel;
  TestFlutterPluginDemo.init(int id){
    _channel = new MethodChannel('test_flutter_plugin_demo');
    _channel.setMethodCallHandler(platformCallHandler);///设置原生参数监听
  }

  ///Flutter 调用原生
  ///这里我传了一个 字符串 当然也可以传Map
  Future changeNativeTitle(String str) async{
    return _channel.invokeListMethod('changeNativeTitle',str);
  }

  ///实现监听原生方法回调
  Future platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "clickAciton":
        print('收到原生回调 ---- $call.arguments');
        return ;
        break;
    }
  }
}

然后让我们在main.dart 里面添加一个按钮用来触发 调用原生

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {

  ///定义一个测试类的属性 用来调用原生方法 和原生交互
  var testFlutterPluginDemo;

  @override
  void initState() {
    super.initState();

  }

  @override
  Widget build(BuildContext context) {
    ///初始化 测试视图的类
    TestView testView = new TestView(
      onCreated: onTestViewCreated,
    );

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Column(
          children: [
            Container(
              height: 200,
              width: 400,
              child: testView,///使用原生视图
            ),
            FloatingActionButton( ///添加一个按钮 用来触发原生调用
              onPressed: onNativeMethon, ///点击方法里面调用原生
            )
          ],
        )
      ),
    );
  }

  void onTestViewCreated(testFlutterPluginDemo){
    this.testFlutterPluginDemo = testFlutterPluginDemo;
  }

  void onNativeMethon(){

    this.testFlutterPluginDemo.changeNativeTitle('Flutter 调用原生成功了');
  }

}

Dart 代码写完了 让我们写原生代码吧 在我们刚才创建的testView 的添加一个 button 用来显示Flutter的调用和调用Flutter
然后在onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result 方法里监听Flutter 调用吧

- (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"]) {
        [_button setTitle:call.arguments forState:UIControlStateNormal];
    }
    
}
//调用Flutter 
- (void)flutterMethod{
    [self.channel invokeMethod:@"clickAciton" arguments:@"我是参数"];
}

现在让我们运行看一看效果吧

frc afe3bb7ff8bef65fc40ddd83cbc00d6b - Flutter开发之Flutter插件开发一
246050b9-fc17-48c4-b482-35a07d869e8c.gif

frc f7335e34391086e276422492fe3b8ebe - Flutter开发之Flutter插件开发一
12.jpg

到现在位置 插件开发就完成了,根据自己需要添加代码就行了,关于插件的发布 咱们在下篇文章中在做详细的讲解
时间不早了,去码代码去了

Flutter开发之Flutter插件开发一