欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

flutter_plugin开发

程序员文章站 2022-04-30 19:16:54
2019-09-16文章目录Create the packageImplement the packageDefine the package APIAdd Android platform codeAdd iOS platform codeFlutter嵌入原生控件在 flutter侧在Android侧在iOS侧Adding documentationAdding licenses to the LICENSE filePublishing packages一个开发Flutter plugin 和...

2019-09-16

一个开发Flutter plugin 和 在Flutter中嵌入原生控件的笔记。完全是照着的官网来实践的。

https://flutter.dev/docs/development/packages-and-plugins/developing-packages

Flutter中的package分为两种,一种是纯dart语言的的package,比如 fluro,称之为Dart packages

还有一种是由原生平台代码(Android:java or kotlin,iOS:OC or swift),比如battery,称之为Plugin packages

下面是对Plugin packages开发的实践

Create the package

创建工程,命令行

flutter create --org com.example --template=plugin name

默认Android是java,iOS是OC,也可以指定默认语言

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

也可以同过AS创建,选择Flutter plugin就好。

主要文件如下(以工程根目录为基础目录来说的):

  • lib

    刚创建好的项目中,该文件夹下只有一个以项目名命名的dart文件,创建了一个MethodChannel 和实现了一个platformVersion方法

  • android

    这里需要注意一下,在AndroidStudio中以Project方式预览工程时,该目录下的src.main文件夹下只有一个清单文件,可以切换到Android方式预览。可以看到src.main下有一个实现MethodCallHandler接口的类,并且已经实现了getPlatformVersion的调用,我们开发插件原生代码就是在这里写的

  • iOS

    在AndroidStudio下看到只有两个文件夹Assets和Classes,在Classes下有个.h.m,也是创建了FlutterMethodChannel和实现了platformVersion调用

  • Example

    在这里测试我们自己写的插件,并且给使用这提供示例。该文件夹下包含Android、iOS和Flutter代码

Implement the package

As a plugin package contains code for several platforms written in several programming languages, some specific steps are needed to ensure a smooth experience.

Define the package API

假如我们要开发一个打开某些原生界面的插件,比如打开原生设置页面、拨号页面、浏览器等

lib下的dart文件中,增加一个名字为openOSView的方法调用,并且传递一个String类型的url参数

class FlutterPluginOpenNative {
  static const MethodChannel _channel =
      const MethodChannel('flutter_plugin_open_native');

  static Future<String> get platformVersion async {
    final String version = await _channel.invokeMethod('getPlatformVersion');
    return version;
  }

  static Future<void>  openNative(String url) async {
    await _channel.invokeMethod('openOSView',url);
  }
}

Add Android platform code

在Android下实现了MethodCallHandler接口的类中

@Override
  public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("getPlatformVersion")) {
      result.success("Android " + android.os.Build.VERSION.RELEASE);
    } else if(call.method.equals("openOSView")){
      Intent intent = new Intent();
      intent.setAction(Intent.ACTION_VIEW);
      intent.setData(Uri.parse(call.arguments.toString()));
      activity.startActivity(intent);
    }
    else {
      result.notImplemented();
    }
  }

Add iOS platform code

在iOS

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
    if ([@"getPlatformVersion" isEqualToString:call.method]) {
        result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
    }
    else if([@"openOSView" isEqualToString:call.method]){
        NSString * phoneUri = call.arguments;
        [[UIApplication sharedApplication] openURL:[NSURL URLWithString:phoneUri]];
    }
    
    else {
        result(FlutterMethodNotImplemented);
    }
}

如果是用XCode开发的话,选择打开example–>ios–>Runner.xcworkspace,该文件存在于

Pods-->Development Pods-->projectname

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KQSkUOsy-1603931221056)(/image/flutter/flutter_plugin_project_in_xcode.png)]

第一个红框是项目中example–>ios中的内容

第二个红框是项目中ios文件夹下内容


这样,一个简单的插件就开发完了,我们可以在example中测试一下,由于插件特别简单,不需要在原生侧做初始化之类的工作,使用创建项目时生成的代码就可以满足我们的需要。

example-->lib-->main.dart中放一个按钮,点击的时候调用FlutterPluginOpenNative.openNative("https://blog.huangyuanlove.com");然后运行到设备,查看一下效果

flutter run -d all 可以运行到所有已连接的设备


Flutter嵌入原生控件

上面是开发的插件,下面是如果在flutter中嵌入原生控件

这里用到了Flutter中的两个类AndroidViewUiKitView,懒得翻译直接看官方版https://api.flutter.dev/flutter/widgets/AndroidView-class.html 和https://api.flutter.dev/flutter/widgets/UiKitView-class.html

例如,我们要嵌入原生展示文字的控件,对于Android来讲,一般是TextView,对于iOS来讲,一般是UILabel。

在 flutter侧

一般是一个Controller和一个StatefulWidget,当然这里的Controller只是一个普通类,用来创建channel和原生通信

代码如下


typedef void TextViewCreatedCallback(TextViewController controller);
class TextViewController {
  TextViewController._(int id)
      : _channel = new MethodChannel('me.chunyu.textview/textview');

  final MethodChannel _channel;

  //调用setText方法给控件设置文字,需要原生侧进行实现
  Future<void> setText(String text) async {
    assert(text != null);
    return _channel.invokeMethod('setText', text);
  }
}

class AndroidTextView extends StatefulWidget {

  final TextViewCreatedCallback onTextViewCreated;
  const AndroidTextView({
    Key key,
    this.onTextViewCreated,
  }) : super(key: key);

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

//对Android平台返回AndroidView,对iOS平台返回UiKitView,并且制定viewType
class _AndroidTextViewState extends State<AndroidTextView> {
  @override
  Widget build(BuildContext context) {
    if (defaultTargetPlatform == TargetPlatform.android) {
      return AndroidView(
        viewType: 'me.chunyu.textview/textview',
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    }else if (defaultTargetPlatform == TargetPlatform.iOS){
      return UiKitView(
        viewType: 'me.chunyu.textview/textview',
        onPlatformViewCreated: _onPlatformViewCreated,
      );
    }
    return Text(
        '$defaultTargetPlatform is not yet supported by the text_view plugin');

  }
  void _onPlatformViewCreated(int id) {
    if (widget.onTextViewCreated == null) {
      return;
    }
    print("id _onPlatformViewCreated :--> $id");


    widget.onTextViewCreated(new TextViewController._(id));
  }
}

在Android侧

我们需要一个实现了PlatformViewFactory的类 和一个实现了PlatformViewMethodCallHandler接口的类


class TextViewFactory extends PlatformViewFactory{

  private final BinaryMessenger messenger;

  public TextViewFactory(BinaryMessenger messenger) {
    super(StandardMessageCodec.INSTANCE);
    this.messenger = messenger;
  }

  @Override
  public PlatformView create(Context context, int id, Object o) {
    return new FlutterTextView(context, messenger, id);
  }

}

class FlutterTextView implements PlatformView, MethodCallHandler  {
  private final TextView textView;
  private final MethodChannel methodChannel;

  FlutterTextView(Context context, BinaryMessenger messenger, int id) {
    Log.e("id","id :-->" +id);
    textView = new TextView(context);
    textView.setTextColor(Color.BLUE);
    textView.setBackgroundColor(Color.GREEN);
    methodChannel = new MethodChannel(messenger, "me.chunyu.textview/textview");
    methodChannel.setMethodCallHandler(this);
  }

  @Override
  public View getView() {
    return textView;
  }

  //实现以下flutter通过channel调用的`setText`方法
  @Override
  public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
    switch (methodCall.method) {
      case "setText":
        setText(methodCall, result);
        break;
      default:
        result.notImplemented();
    }

  }

  //设置文字,并返回成功
  private void setText(MethodCall methodCall, Result result) {
    String text = (String) methodCall.arguments;
    textView.setText(text);
    result.success(null);
  }

  @Override
  public void dispose() {}
}

这里需要注意,在插件的registerWith方法中注册一下channel

registrar.platformViewRegistry().registerViewFactory("me.chunyu.textview/textview",new TextViewFactory(registrar.messenger()));

在iOS侧

一个.h文件,声明一下实现NSObject<FlutterPlatformView>

#import <Foundation/Foundation.h>
#import <Flutter/Flutter.h>
NS_ASSUME_NONNULL_BEGIN

@interface AndroidTextView : NSObject<FlutterPlatformView>
- (instancetype)initWithWithFrame:(CGRect)frame
                   viewIdentifier:(int64_t)viewId
                        arguments:(id _Nullable)args
                  binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;

@end
@interface FlutterAndroidTextViewFactory : NSObject<FlutterPlatformViewFactory>

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger>*)messager;

@end

NS_ASSUME_NONNULL_END

一个.m文件,实现以上方法


#import "AndroidTextView.h"

@implementation FlutterAndroidTextViewFactory{
    NSObject<FlutterBinaryMessenger>*_messenger;
}

- (instancetype)initWithMessenger:(NSObject<FlutterBinaryMessenger> *)messager{
    self = [super init];
    if (self) {
        _messenger = messager;
    }
    return self;
}

-(NSObject<FlutterMessageCodec> *)createArgsCodec{
    return [FlutterStandardMessageCodec sharedInstance];
}

-(NSObject<FlutterPlatformView> *)createWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args{
    AndroidTextView * androidTextView = [[AndroidTextView alloc] initWithWithFrame:frame viewIdentifier:viewId arguments:args binaryMessenger:_messenger];
    
    return androidTextView;
    
}

@end


@implementation AndroidTextView{
    int64_t _viewId;
    FlutterMethodChannel* _channel;
    UILabel * _label;
    
}

- (instancetype)initWithWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id)args binaryMessenger:(NSObject<FlutterBinaryMessenger> *)messenger{
    if ([super init]) {
        _label = [[UILabel alloc] init];
        _label.textColor = UIColor.redColor;
        _label.backgroundColor = UIColor.blueColor;
        _label.font = [UIFont fontWithName:@"Arial" size:30];
        _viewId = viewId;
        NSString* channelName =  @"me.chunyu.textview/textview";
        _channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:messenger];
        __weak __typeof__(self) weakSelf = self;
        [_channel setMethodCallHandler:^(FlutterMethodCall *  call, FlutterResult  result) {
            [weakSelf onMethodCall:call result:result];
        }];   
    }
    
    return self;
}

-(UIView *)view{
    NSLog(@"invoke view()");
    return _label;
}
//实现flutter侧通过channel调用的`setText`方法
-(void)onMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result{
    if ([[call method] isEqualToString:@"setText"]) {
        _label.text = [call arguments];
        NSLog(@"%@", call.arguments);
    }
}
@end

在plugin中的registerWithRegistrar方法中注册一下channel

[registrar registerViewFactory:[[FlutterAndroidTextViewFactory alloc] initWithMessenger:registrar.messenger] withId:@"me.chunyu.textview/textview"];

需要注意的是,如果想要在iOS中显示这种flutter嵌入原生的空间,需要在info.plist文件中加入

<key>io.flutter.embedded_views_preview</key>
<true/>

在flutter中嵌入原生控件并不推荐,性能跟不上,消耗太大了,但是在某些情况下不得不这么搞。。。。。

Adding documentation

这个没啥好说的,跟着官方做就好了

When you publish a package, API documentation is automatically generated and published to dartdocs.org, see for example the device_info docs

Adding licenses to the LICENSE file

添加协议

Publishing packages

发布之前运行一下 flutter pub pub publish --dry-run,根据提示修改不满足需求的地方。

之后执行flutter pub pub publish,具体可以看For details on publishing, see the publishing docs for the Pub site.

本文地址:https://blog.csdn.net/huangyuan_xuan/article/details/109350484

相关标签: Flutter android