博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Flutter之在Flutter布局中嵌入原生组件Android篇
阅读量:6072 次
发布时间:2019-06-20

本文共 8084 字,大约阅读时间需要 26 分钟。

之前介绍过在原生工程内嵌入Flutter,以页面形式或者View的形式嵌入都是可以的,最近看Flutter源码发现Flutter还支持在Flutter布局中嵌入原生View,这个特性在文档中还没有介绍,但是确实是一个非常实用的特性,比如困扰已久的地图实现,有了这个特性我们就可以在Flutter布局中嵌入双平台的原生高德地图或百度地图,甚至是相机预览视频通话SDK等。

本篇一个简单的TextView为示例,介绍如何在Flutter工程中嵌入原生组件。

创建Flutter工程

原生组件扩展比较规范的写法是创建插件工程,然后让Flutter工程引入插件工程使用,本篇为了方便,直接在Flutter工程编写组件并注册,插件工程的开发以后再介绍。

使用AndroidStudio创建一个普通的Flutter工程,修改main.dar文件,移除不必要的代码便于演示,整理后代码如下:

void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      title: 'Flutter Demo',      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: MyHomePage(title: 'Flutter Demo'),    );  }}class MyHomePage extends StatefulWidget {  MyHomePage({Key key, this.title}) : super(key: key);  final String title;  @override  _MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State
{ @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( ), ); }}复制代码

在Android工程中编写并注册原生组件

添加原生组件的流程基本是这样的:

1.实现原生组件PlatformView提供原生view 2.创建PlatformViewFactory用于生成PlatformView 3.创建FlutterPlugin用于注册原生组件

创建原生组件

在FLutter工程生成了几个文件夹,lib是放Flutter工程代码,android和ios文件夹分别是对应的双平台的原生工程,这里直接打开Android工程目录,项目默认生成了GeneratedPluginRegistrant和MainActivity两个文件,GeneratedPluginRegistrant不要动,在和MainActivity的包下新建自定义View,Flutter的原生View不能直接继承自View,需要实现提供的PlatformView接口:

public class MyView implements PlatformView {    private final TextView myNativeView;    MyView(Context context, BinaryMessenger messenger, int id, Map
params) { TextView myNativeView = new TextView(context); myNativeView.setText("我是来自Android的原生TextView"); this.myNativeView = myNativeView; } @Override public View getView() { return myNativeView; } @Override public void dispose() { }}复制代码

这是一个包装类,在实现的getView方法中返回原生的View对象给Flutter,这里便于演示,返回一个TextView。

创建PlatformViewFactory

接下来创建PlatformViewFactory,创建一个类继承自PlatformViewFactory:

public class MyViewFactory extends PlatformViewFactory {    private final BinaryMessenger messenger;    public MyViewFactory(BinaryMessenger messenger) {        super(StandardMessageCodec.INSTANCE);        this.messenger = messenger;    }    @SuppressWarnings("unchecked")    @Override    public PlatformView create(Context context, int id, Object args) {        Map
params = (Map
) args; return new MyView(context, messenger, id, params); }复制代码

在create方法中能够获取到三个参数,args是由Flutter传过来的自定义参数,这里暂时用不到。

注册插件

创建一个插件类MyViewFlutterPlugin,并在类的静态方法中写上注册逻辑供调用:

public class MyViewFlutterPlugin {    public static void registerWith(PluginRegistry registry) {        final String key = MyViewFlutterPlugin.class.getCanonicalName();        if (registry.hasPlugin(key)) return;        PluginRegistry.Registrar registrar = registry.registrarFor(key);        registrar.platformViewRegistry().registerViewFactory("plugins.nightfarmer.top/myview", new MyViewFactory(registrar.messenger()));    }}复制代码

上面代码中使用了plugins.nightfarmer.top/myview这样一个字符串,这是组件的注册名称,在Flutter调用时需要用到,你可以使用任意格式的字符串。 在MainActivity的onCreate方法中增加注册调用

MyViewFlutterPlugin.registerWith(this);复制代码

因为这里是直接在Flutter工程中编写的,所以也可以直接把注册逻辑写在Activity中,为了和插件工程的注册流程保持一致,还是建议抽出来写。

在Flutter工程中调用原生View

原生View的调用非常简单,在使用Android平台的view只需要创建AndroidView组件并告诉它组件的注册注册名称即可:

class _MyHomePageState extends State
{ @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: AndroidView(viewType: 'plugins.nightfarmer.top/myview'), ), ); }}复制代码

因为只是实现了Android平台,所以这里直接调用了AndroidView,如果你是双平台的实现,则可以通过引入package:flutter/foundation.dart包,并判断defaultTargetPlatformTargetPlatform.android还是TargetPlatform.iOS来引入不同平台的实现。

给原生view增加参数

某些情况下,需要给原生组件提供一些初始化参数,比如webview的url,比如地图的中心坐标,又比如上面示例的中文本内容,我们传入一个map即可实现:

class _MyHomePageState extends State
{ @override Widget build(BuildContext context) { print(defaultTargetPlatform); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: AndroidView( viewType: 'plugins.nightfarmer.top/myview', creationParams: { "myContent": "通过参数传入的文本内容", }, creationParamsCodec: const StandardMessageCodec(), ), ), ); }}复制代码

creationParams传入了一个map参数,并由原生组件接收,creationParamsCodec传入的是一个编码对象这是固定写法。 然后在原生组件中接收参数并初始化TextView的文本:

public class MyView implements PlatformView {    private final TextView myNativeView;    MyView(Context context, BinaryMessenger messenger, int id, Map
params) { TextView myNativeView = new TextView(context); myNativeView.setText("我是来自Android的原生TextView"); this.myNativeView = myNativeView; if (params.containsKey("myContent")) { String myContent = (String) params.get("myContent"); myNativeView.setText(myContent); } } ...}复制代码

有一点需要注意的是,原生组件初始化的参数并不会随着setState重复赋值,也就是说这种是init参数。

关于如何更改已经实例化的原生组件的状态,可以通过MethodCall来实现,看下面

通过MethodChannel与原生组件通讯

首先让原始组件实现MethodCallHandler接口:

public class MyView implements PlatformView, MethodChannel.MethodCallHandler {    private final TextView myNativeView;    MyView(Context context, BinaryMessenger messenger, int id, Map
params) { ... MethodChannel methodChannel = new MethodChannel(messenger, "plugins.nightfarmer.top/myview_" + id); methodChannel.setMethodCallHandler(this); } @Override public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { // 在接口的回调方法中可以接收到来自Flutter的调用 } ...}复制代码

然后在dart代码中做如下处理:

class _MyHomePageState extends State
{ @override Widget build(BuildContext context) { print(defaultTargetPlatform); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: AndroidView( viewType: 'plugins.nightfarmer.top/myview', creationParams: { "myContent": "通过参数传入的文本内容", }, creationParamsCodec: const StandardMessageCodec(), onPlatformViewCreated: onMyViewCreated, ), ), ); } MethodChannel _channel; void onMyViewCreated(int id) { _channel = new MethodChannel('plugins.nightfarmer.top/myview_$id'); setMyViewText(); } Future
setMyViewText(String text) async { assert(text != null); return _channel.invokeMethod('setText', text); }}复制代码

通过onPlatformViewCreated回调,监听原始组件成功创建,并能够在回调方法的参数中拿到当前组件的id,这个id是系统随机分配的,然后通过这个分配的id加上我们的组件名称最为前缀创建一个和组件通讯的MethodChannel,拿到channel对象之后就可以通过invokeMethod方法向原生组件发送消息了,这里这里发送的是‘setText’这个消息,并带上文本内容,下面在原生组件中处理消息的接收逻辑。

@Override    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {        if ("setText".equals(methodCall.method)) {            String text = (String) methodCall.arguments;            myNativeView.setText(text);            result.success(null);        }    }复制代码

onMethodCall的处理方式和正常的插件扩展是一致的,这里不再赘述。

性能如何

通过一个ListView来实例化多个原生组件,看看效果如何:

class _MyHomePageState extends State
{ @override Widget build(BuildContext context) { print(defaultTargetPlatform); return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: ListView.builder( itemBuilder: (context, index) { return Container( child: AndroidView( viewType: 'plugins.nightfarmer.top/myview', creationParams: { "myContent": "通过参数传入的文本内容$index", }, creationParamsCodec: const StandardMessageCodec(), ), height: 100, ); }, itemCount: 100, ), ); }}复制代码

这样写虽然跑起来了,ListView也确实能够正常滑动,但是能够感受到明显的掉帧,可见在一个界面中实例化多个原生组件的情况对性能的影响非常的大,也不建议在实际开发中大量引入原生组件,因为除去地图/WebView等特殊情况,基本上原生能实现的UI效果Flutter的UI引擎都能实现。

在开发原生组件时,Flutter的热加载是无效的,因为每次都需要编译原生工程才能使之生效。另外我这里的Mac环境用Genymotion是无法正常运行的,需要使用真机并不使用--enable-software-rendering参数才可以。

本篇完。


更多干货移步我的个人博客

转载地址:http://jcigx.baihongyu.com/

你可能感兴趣的文章
html 富文本编辑器相关--中文状态下输入@的问题
查看>>
在这里给大家安利一个好的免费的虚拟主机云服务器使用地址
查看>>
Linux下使用wc统计文件行数
查看>>
java.lang.UnsupportedClassVersionError: com/T : Unsupported major.minor version问题解决
查看>>
【GO】golang 方法的值调用和指针调用
查看>>
Devexpress ChartControl 柱状图简单例子
查看>>
n元线性方程非负整数解的个数问题
查看>>
技术题目收集整理
查看>>
想象力比知识更重要么?提出问题比解决问题更重要?
查看>>
iOS边练边学--父子控制器之自定义控制器的切换
查看>>
八个防止浪费时间的小窍门
查看>>
^ 转义字符
查看>>
java面向对象入门之带参方法创建
查看>>
Eclipse反编译插件jad的安装
查看>>
动态加载swiper,默认显示最后一个swiper-slide
查看>>
css 让一张彩色的图片变成一张黑白图
查看>>
Git介绍和基本原理
查看>>
后缀数组 3160 最长公共子串
查看>>
计算机IDE接口与SATA接口的区别
查看>>
PLSQL_基础系列05_视图控制WITH CHECK OPTION(案例)
查看>>