程序地带

Flutter2.0插件的编写(Android)



文章目录
一、前言二、Android代码的编写三、插件仓库四、加载插件五、编写Android端的自定义View六、创建工厂模式对自定义View进行加载七、在插件中加载PlatformView八、Flutter中进行显示Native的自定义View九、在原生的VIew中获取Activity十、混合通信及完整代码十一、总结十二、参考资料


一、前言

​ Flutter本身是一个跨平台的框架,所以不可能面面俱到的把Native平台的特性都实现出来,这里面就需要用到插件。本文基于Android平台来实现一个Android客户端自定义的View,并可以进行一定的混合通信能力


二、Android代码的编写

​ 首先编写Plugin插件,新版的插件是通过实现FlutterPlugin来做的,简单代码如下:


TestPlugin.kt


import io.flutter.embedding.engine.plugins.FlutterPlugin
/**
* 测试插件
*/
class TestPlugin: FlutterPlugin{
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在加载插件会调用该函数
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在卸载插件时候会调用该函数
}
}
三、插件仓库

​ 上一段内容就是简单的编写了一个插件,虽然什么功能都没有,接下来需要在程序启动时候进行加载插件,这里我们编写一个统一的插件管理仓库来进行对插件进行管理


DqPluginRegistrant.kt


import androidx.annotation.Keep
import com.dq.flutter_dq_app.plugin.TestPlugin
import io.flutter.embedding.engine.FlutterEngine
/**
* 自己写的插件在这里进行注册
*/
@Keep
class DqPluginRegistrant {
companion object{
fun registerWith(flutterEngine: FlutterEngine){
flutterEngine.plugins.add(TestPlugin())//通过FlutterEngine进行加载插件
}
}
}
四、加载插件

​ 由上文知道插件需要一个FlutterEngine来进行加载的,一般可以在FlutterActivity和FlutterFragmentActivity中获取到该值,代码如下:


import android.content.Intent
import android.util.Log
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterFragmentActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
DqPluginRegistrant.registerWith(flutterEngine)
}
}

至此,整个插件的加载就已经完成了,在Flutter程序启动时候,会把作为首页的MainActivity启动,然后通过调用configureFlutterEngine(flutterEngine: FlutterEngine)进行插件配置。


五、编写Android端的自定义View

在Flutter中可以通过实现PlatformView来实现对原生View的封装,使原生View作为Flutter组件进行显示,简要代码如下:


import android.content.Context
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
//自定义View
class TestView: PlatformView{
private var view:View
constructor(context: Context) {
val textView = TextView(context)
textView.text = "这是一个TextView"
this.view = textView
}
override fun getView(): View = view
override fun dispose() {
//这里可以对一些资源进行清理
}
}
六、创建工厂模式对自定义View进行加载

在Flutter中加载PlatformView的需要通过PlatformViewFactory来进行加载,简要代码如下:


import android.content.Context
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
class FlutterTestViewFactory: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
//参数中args的问号不可以省略,否则程序出错
override fun create(context: Context, viewId: Int, args: Any?): PlatformView = FlutterTestView(context)
}
七、在插件中加载PlatformView

PlatformViewFactory创建完后需要在插件中进行加载,代码如下:


import com.dq.flutter_dq_app.view.FlutterTestViewFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin
/**
* 测试插件
*/
class TestPlugin:
FlutterPlugin//对Flutter加载、卸载的监听
{
private val gameViewType = "flutter.plugins.io/testView"//本地View的类型,这个名字要和Flutter那边的组件名字保持一致
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在加载插件会调用该函数
binding
.platformViewRegistry
.registerViewFactory(
gameViewType, FlutterTestViewFactory())
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在卸载插件时候会调用该函数
}
}
八、Flutter中进行显示Native的自定义View

简单代码如下:


import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: Text('Material App Bar'),
),
body: Center(
child: Container(
child: AndroidView(viewType: 'flutter.plugins.io/testView'),//这个名字要和Native那边的名字保持一致
),
),
),
);
}
}

至此,通过插件编写原生View的方式基本结束


九、在原生的VIew中获取Activity

在实际开发中业务往往是复杂的,里面常常会用到Activity,针对这个问题,可以通过Android的ActivityLifecycleCallbacks获取顶层View的方式来实现,当然也可以通过Flutter自带的方式,接下来使用Flutter来在View中获取Activity,这里需要使用到ActivityAware,并且需要对原先的代码进行改造,代码如下:


FlutterTestView.kt


//自定义View
class FlutterTestView: PlatformView{
private var view:View
constructor(context: Context,act: Activity? = null) {
val textView = TextView(context)
textView.text = "这是一个TextView"
this.view = textView
if(null != act){
Toast.makeText(act,"获取到了Activity",Toast.LENGTH_SHORT).show();
}
}
override fun getView(): View = view
override fun dispose() {
//这里可以对一些资源进行清理
}
}

FlutterTestViewFactory.kt


//自定义View的工厂方法
class FlutterTestViewFactory: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
private var activity: Activity? = null
//参数中的问号不可以省略,否则程序出错
//create函数是在Flutter中显示该自定义组件的时候进行回调,请注意这个时机
override fun create(context: Context, viewId: Int, args: Any?): PlatformView{
return FlutterTestView(context,activity)
}
//之所以分开而不在构造函数里面传递是因为各个回调函数调用时机不一致的问题
fun setActivity(activity: Activity){
this.activity = activity
}
}

TestPlugin.kt


import com.dq.flutter_dq_app.view.FlutterTestViewFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
/**
* 测试插件
*/
class TestPlugin:
FlutterPlugin,//对Flutter加载、卸载的监听
ActivityAware//对Activity加载、卸载的监听
{
private val gameViewType = "flutter.plugins.io/testView"//本地View的类型,这个名字要和Flutter那边的组件名字保持一致
private lateinit var flutterTestViewFactory: FlutterTestViewFactory
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在加载插件会调用该函数
flutterTestViewFactory = FlutterTestViewFactory()
binding
.platformViewRegistry
.registerViewFactory(
gameViewType, flutterTestViewFactory)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在卸载插件时候会调用该函数
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
//加载Activity的监听
//该函数会在onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding)之后调用
flutterTestViewFactory.setActivity(binding.activity)
}
override fun onDetachedFromActivityForConfigChanges() {
//配置切换监听,比如横竖屏旋转
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
//配置切换监听,比如横竖屏旋转
}
override fun onDetachedFromActivity() {
//移除Activity的监听
//该函数会在onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding)之前调用
}
}
十、混合通信及完整代码

在实际业务中,通常不仅仅只显示一个View这样,还有其它一些交互操作,这里就涉及到通信问题,以下来通过Flutter端的点击事件来改变Native端View显示的内容,完整代码如下:


FlutterTestView.kt


//自定义View
class FlutterTestView: PlatformView{
private var view:View
constructor(context: Context,act: Activity? = null) {
val textView = TextView(context)
textView.text = "这是一个TextView"
this.view = textView
Toast.makeText(act,"获取到了Activity",Toast.LENGTH_SHORT).show();
}
override fun getView(): View = view
override fun dispose() {
//这里可以对一些资源进行清理
}
fun changeText(text: String){
if (view is TextView){
(view as TextView).text = text
}
}
}

FlutterTestViewFactory.kt


//自定义View的工厂方法
class FlutterTestViewFactory: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
private var activity: Activity? = null
private lateinit var flutterTestView: FlutterTestView
//参数中的问号不可以省略,否则程序出错
//create函数是在Flutter中显示该自定义组件的时候进行回调,请注意这个时机
override fun create(context: Context, viewId: Int, args: Any?): PlatformView{
flutterTestView = FlutterTestView(context,activity)
return flutterTestView
}
//之所以分开而不在构造函数里面传递是因为各个回调函数调用时机不一致的问题
fun setActivity(activity: Activity){
this.activity = activity
}
fun changeText(text: String){
flutterTestView.changeText(text)
}
}

TestPlugin.kt


class TestPlugin:
FlutterPlugin,//对Flutter加载、卸载的监听
ActivityAware,//对Activity加载、卸载的监听
MethodChannel.MethodCallHandler//对Flutter与Native通信的监听
{
private val gameViewType = "flutter.plugins.io/testView"//本地View的类型,这个名字要和Flutter那边的组件名字保持一致
//Flutter与Native通信的渠道,调用View中的函数时候,由于View可能不存在,所以可以将相关的通信渠道转移到View中进行监听
private val gameViewChannel = "dq.plugins.flutter/testView/channel"//Flutter那里对通信渠道监听的时候需要和该渠道名字保持一致
private val methodName = "testMethod"//Flutter那里调用的函数名字需要和这个保持一致
private lateinit var flutterTestViewFactory: FlutterTestViewFactory
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在加载插件会调用该函数
val gameChannel = MethodChannel(binding.binaryMessenger, gameViewChannel)
gameChannel.setMethodCallHandler(this)//对通信渠道的监听
flutterTestViewFactory = FlutterTestViewFactory()
binding
.platformViewRegistry
.registerViewFactory(
gameViewType, flutterTestViewFactory)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
//程序在卸载插件时候会调用该函数
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
//加载Activity的监听
//该函数会在onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding)之后调用
flutterTestViewFactory.setActivity(binding.activity)
}
override fun onDetachedFromActivityForConfigChanges() {
//移除Activity的监听
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
}
override fun onDetachedFromActivity() {
//该函数会在onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding)之前调用
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
if (call.method == methodName) {//通信渠道中调用相关的函数
result.success("Android ${android.os.Build.VERSION.RELEASE}")//该方法会将内容回传给Flutter
val arguments = call.arguments as String //传递什么都行,这里记得改成一样的类型
flutterTestViewFactory.changeText(arguments)
} else {
result.notImplemented()
}
}
}

flutter_native_view.dart


import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: Scaffold(
appBar: AppBar(
title: Text('Material App Bar'),
),
body: Center(
child: Container(
child: GestureDetector(
onTap: (){
//这里接收不到
print("Android控件点击情况");
},
onVerticalDragDown: (d){//示例代码源于platform_view.dart
print("垂直滑动事件");
platformTest;
},
child: Container(
height: 200,
width: 200,
color: Colors.red,
//这个名字要和Native那边的名字保持一致
//PlatformViews没有办法接收到点击事件,但是底层的屏幕事件是可以接收到的
//详情看:https://zhuanlan.zhihu.com/p/108770601
child: AndroidView(viewType: 'flutter.plugins.io/testView'),
),
),
)
),
),
);
}
static const MethodChannel _channel = const MethodChannel('dq.plugins.flutter/testView/channel');//参数记得改成和Native端一致的内容
Future<String> get platformTest async {
final String version = await _channel.invokeMethod('testMethod',"我要改值了");//第一个方法名记得和Native保持一致
return version;
}
}
十一、总结

以上实现了日常开发中的简单的Flutter和Native的交互问题,但是其中还有一些问题需要了解,比如ActivityAware类的含义以及与其类似的其它类,如:BroadcastReceiverAware、ContentProviderAware、ServiceAware。再比如通信中用到的MethodChannel以及其他相关的类,如:BasicMessageChannel等等,还有Flutter中是否可以嵌入Android的Fragment等问题。


十二、参考资料

本文主要参考了部分源码、官方示例代码及以下资料:


https://book.flutterchina.club/chapter12/android_implement.htmlhttps://medium.com/flutter/modern-flutter-plugin-development-4c3ee015cf5ahttps://www.jianshu.com/p/bba0f615d59chttps://www.cnblogs.com/moluy/p/14132564.htmlhttps://www.jianshu.com/p/7367492e7bf1三种MessageChannel的使用方式: https://www.cnblogs.com/wjw334/p/12693220.htmlFlutter混合开发-通信: https://www.cnblogs.com/wjw334/archive/2004/01/13/12693220.html

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Mr_Tony/article/details/111246269

随机推荐

我在阿里巴巴做 Serverless 云研发平台

 凌云时刻·技术导读:Serverless云研发平台是通过提供哪些能力保障各租户业务的快速开发和安全交付的。作者|苏河来源|阿里巴巴中间件前言技术的成熟度源自...

凌云时刻 阅读(252)

常用技巧

打开CMD的方式:开始+系统+命令提示符win+R输入CMD打开控制台在任意的文件夹下,按住shift键+鼠标右键点击,在此处...

寻一个黄昏 阅读(927)

1月13号笔记

冯诺依曼结构冯诺依曼结构就是说一个计算机最简单的结构,I/O设备,存储器,CPU输入设备向存储器输入数据,CPU从存储器提取数据进行计算...

大鼻子rex 阅读(287)

打印两个链表的第一个公共节点

「力扣上剑指offer52,打印两个链表的第一个公共节点。」举个栗子很多问题都有多种算法可以解决。暴力解题最最最简单的就是暴力解题,你说两个链表的第一个公共节点࿰...

不作声 阅读(592)

求职者被字节HR放鸽子?

12月16日,一个名叫“天城犬犬”的微博网友在微博爆料,大致意思是说自己手里已经拿到一家公司offer,但由于字节跳动的HR口头跟他保证他已经面试通过...

liudada8265 阅读(964)

2020-12-19

第一题classSolution{publicListNodeaddTwoNumbers(ListNodel1,ListNodel2){//若有一个数为0,则返回另一个if(l1.val...

渣娃使用者 阅读(730)