为什么要自定义插件?
Flutter正式版本1.0是2018年12月才发布,目前还是算比较新的移动应用开发框架,虽然官方和很多大牛写了很的可以用的插件,但总有些需要你自己去写的插件,除非你的应用非常简单。
比如,你的应用用到了第三方厂商的库,但你发现第三方厂商还没有提够flutter插件,这是就需要你自己去封装一个插件了!
如何自定义一个flutter插件?
自定义一个flutter插件,需要解决两个问题:
1、怎么跟原生平台通讯,也就是怎么调用原生(安卓或ios),和原生又怎么回调Flutter?
2、怎么让原生控件,显示在flutter上?
接下来,我用一个原生的按钮显示在flutter上的例子,来实现一下。
问题分析:
1、flutter怎么给原生按钮设置按钮文字、字体、颜色等等?
flutter和原生通讯主要是通过MethodChannel
2、原生按钮怎么显示在flutter界面上?
flutter提供了一个AndroidView(android),UiKitView(ios)的控件,来实现原生显示在flutter上
3、点击按钮以后,怎么回调在flutter?
回调也是通过MethodChannel 来实现
具体来看一下代码:
1、第一步,新建一个flutter plugin (以android studio为例)
2、在lib目录下创建一个button_view类,这个给是给flutter直接使用的widget
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
//自定义一个OnClickListener回调
typedef void OnClickListener(Widget view);
typedef void ButtonCreatedCallback(ButtonController controller);
//自定Flutter可以调用的ButtonWidget
class Button extends StatefulWidget {
//定义属性
final String text;
//final double width;
//final double height;
//定义一个点击事件
final OnClickListener onListener;
//定义一个回调
final ButtonCreatedCallback onButtonCreatedCallback;
// 实现一个构造函数 text,width,height 为必传
const Button({Key key,
@required this.text,
this.onListener,
this.onButtonCreatedCallback})
: super(key: key);
@override
State<StatefulWidget> createState() {
return ButtonState();
}
}
3、实现一个ButtonState
class ButtonState extends State<Button> {
@override
Widget build(BuildContext context) {
//安卓平台
if (defaultTargetPlatform == TargetPlatform.android) {
//AndroidView 是包装原生View的关键widget
return new AndroidView(
viewType: 'plugins.metre.com/button', // 自定义唯一viewType
onPlatformViewCreated: _onPlatformViewCreated,
creationParams: _creationParams(),
creationParamsCodec: const StandardMessageCodec()//如果设置了creationParams参数,必须要设置creationParamsCodec
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
//IOS平台
//to do list
return null;
}
//该插件暂时还不支持该平台
return Text(
'$defaultTargetPlatform is not yet supported by the the button plugin!');
}
//封装参数
Map<String, dynamic> _creationParams() {
return <String, dynamic>{
'text': widget.text
};
}
void _onPlatformViewCreated(int id) {
ButtonController controller = ButtonController._(id, widget);
if (widget.onButtonCreatedCallback != null){
widget.onButtonCreatedCallback(controller);
}
}
}
4、实现一个ButtonState
//Button回调控制
class ButtonController {
final MethodChannel _channel;
final Button _widget;
ButtonController._(int id, this._widget,) : _channel = MethodChannel('plugins.metre.com/button_$id'){
_channel.setMethodCallHandler(_methodCallHandler);
}
Future<bool> _methodCallHandler(MethodCall call) {
switch (call.method) {
case 'onClickListener':
_widget.onListener(_widget);
return null;
}
//没有该回调,抛异常
throw MissingPluginException(
'${call.method} was invoked but has no handler!');
}
}
以上四部基本上就完成了flutter端的编程。
接下来就是android端实现了
目前android目录下会生成一个xxxPlugin的问题
package com.metre.buttonplugin;
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;
/** ButtonPlugin */
public class ButtonPlugin implements MethodCallHandler {
/** Plugin registration. */
public static void registerWith(Registrar registrar) {
final MethodChannel channel = new MethodChannel(registrar.messenger(), "button_plugin");
channel.setMethodCallHandler(new ButtonPlugin());
//注册自己实现的Factory
registrar.platformViewRegistry().registerViewFactory("plugins.metre.com/button",new ButtonFactory(registrar.messenger()));
}
@Override
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getPlatformVersion")) {
result.success("Android " + android.os.Build.VERSION.RELEASE);
} else {
result.notImplemented();
}
}
}
实现ButtonFactory
package com.metre.buttonplugin;
import android.content.Context;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
/**
* Created by mi on 2019/5/20
*/
@SuppressWarnings("ALL")
class ButtonFactory extends PlatformViewFactory {
private final BinaryMessenger messenger;
ButtonFactory(BinaryMessenger messenger) {
super(StandardMessageCodec.INSTANCE);
this.messenger = messenger;
}
@Override
public PlatformView create(Context context, int id, Object params) {
return new ButtonPlatformView(context,id,(Map<String,Object>)params,messenger);
}
}
实现ButtonPlatformView
package com.metre.buttonplugin;
import android.content.Context;
import android.view.View;
import android.widget.Button;
import java.util.Map;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;
/**
* Created by mi on 2019/5/20
*/
class ButtonPlatformView implements PlatformView {
private Context context;
private Button button;
private MethodChannel channel;
ButtonPlatformView(Context context, int id, Map<String, Object> params, BinaryMessenger messenger) {
this.context = context;
this.button = new Button(context);
this.channel = new MethodChannel(messenger,"plugins.metre.com/button_"+id);
// 属性参数设置
if(params != null && params.containsKey("text")){
Object text = params.get("text");
button.setText(text.toString());
}
// 其他属性设置....
//设置监听
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
channel.invokeMethod("onClickListener",null);
}
});
}
@Override
public View getView() {
return button;
}
@Override
public void dispose() {
channel.setMethodCallHandler(null);
}
}
以上完成了全部编码
接下来,可以在flutter调用自己写的Button了
打开默认生成的example,加上自己的控件
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: Column(children: <Widget>[
Text('Running on: $_platformVersion\n'),
SizedBox(
width: 200,
height: 50,
//调用自定义Button
child: Button(text:"登录",onListener:(view){
print('Button onListener-----> $view');
})
),
],)
),
),
);
}
调用完成,点击运行在手机上的效果:
点击按钮后打印的日志:
成功完成的原生控件的显示,和点击事件的回调。