前言
文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章
Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客
数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。
效果图
图解
1、Android原生端
1.0 PlatformView
Android:ComputeLayoutPlatform.kt
package com.example.flutter_mix_android.ui.flutterplugin.platform;import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.lifecycle.ViewModelProvider
import com.example.flutter_mix_android.R
import com.example.flutter_mix_android.bean.CountBean
import com.example.flutter_mix_android.databinding.LayoutComputeBinding
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView/*** 封装成PlatformView*/
class ComputeLayoutPlatform(context: Context,rootContext: Context,messenger: BinaryMessenger,viewId: Int,args: Any?,
) : FrameLayout(context), PlatformView, MethodChannel.MethodCallHandler {private lateinit var mChannel: MethodChannelprivate lateinit var bind: LayoutComputeBindingprivate lateinit var viewModel: CountBeancompanion object {// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 获取 Flutter端 数据private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 发送数据private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 获取 Android端 数据}init {initChannel(messenger, viewId)initView()initData(rootContext, args)}/*** 初始化消息通道*/private fun initChannel(messenger: BinaryMessenger, viewId: Int) {// 创建 Android端和Flutter端的,相互通信的通道// 通道名称,两端必须一致mChannel = MethodChannel(messenger, "flutter.mix.android/compute/$viewId")// 监听来自 Flutter端 的消息通道// Flutter端调用了函数,这个handler函数就会被触发mChannel.setMethodCallHandler(this)}/*** 初始化视图*/private fun initView() {LayoutInflater.from(context).inflate(R.layout.layout_compute, this, true)bind = LayoutComputeBinding.bind(getChildAt(0))bind.add.setOnClickListener {val count: Int = viewModel.curNum.value ?: 0viewModel.curNum.value = count + 1}bind.androidSendFlutterData.setOnClickListener {androidSendFlutterData()}bind.androidGetFlutterData.setOnClickListener {androidGetFlutterData()}}/*** Android端 向 Flutter端 发送数据,PUT 操作*/private fun androidSendFlutterData() {val map: MutableMap<String, Int> = mutableMapOf<String, Int>()map["androidNum"] = viewModel.curNum.value ?: 0mChannel.invokeMethod(ANDROID_SEND_FLUTTER_DATA_NOTICE,map,object : MethodChannel.Result {override fun success(result: Any?) {Log.d("TAG", "success:$result")updateFlutterNum((result as? Int) ?: 0)}override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {Log.d("TAG","errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails")}/*** Flutter端 未实现 Android端 定义的接口方法*/override fun notImplemented() {Log.d("TAG", "notImplemented")}})}/*** Android端 获取 Flutter端 数据,GET 操作*/private fun androidGetFlutterData() {// 说一个坑,不传参数可以写null,// 但不能这样写,目前它没有这个重载方法,invokeMethod第二个参数是Object类型,所以编译器不会提示错误// mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, object : MethodChannel.Result {// public void invokeMethod(@NonNull String method, @Nullable Object arguments)mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE,null,object : MethodChannel.Result {override fun success(result: Any?) {Log.d("TAG", "success:$result")updateGetFlutterNum((result as? Int) ?: 0)}override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {Log.d("TAG","errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails")}/*** Flutter端 未实现 Android端 定义的接口方法*/override fun notImplemented() {Log.d("TAG", "notImplemented")}})}/*** 初始化数据*/private fun initData(rootContext: Context, args: Any?) {val owner = rootContext as FlutterFragmentActivityviewModel = ViewModelProvider(owner)[CountBean::class.java]bind.countBean = viewModelbind.lifecycleOwner = owner// 获取初始化时 Flutter端 向 Android 传递的参数val map: Map<String, Int> = args as Map<String, Int>viewModel.getFlutterNum.value = map["flutterNum"]}/*** 监听来自 Flutter端 的消息通道** call: Android端 接收到 Flutter端 发来的 数据对象* result:Android端 给 Flutter端 执行回调的接口对象*/override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {// 获取调用函数的名称val methodName: String = call.methodwhen (methodName) {FLUTTER_SEND_ANDROID_DATA_NOTICE -> {// 回调结果对象// 获取Flutter端传过来的数据val flutterCount: Int? = call.argument<Int>("flutterNum")updateFlutterNum(flutterCount ?: 0)result.success("success")// 回调状态接口对象,里面有三个回调方法// result.success(result: Any?)// result.error(errorCode: String, errorMessage: String?, errorDetails: Any?)// result.notImplemented()}FLUTTER_GET_ANDROID_DATA_NOTICE -> {result.success(viewModel.curNum.value)}else -> {result.notImplemented()}}}fun updateFlutterNum(flutterCount: Int) {viewModel.flutterNum.value = flutterCount}fun updateGetFlutterNum(flutterCount: Int) {viewModel.getFlutterNum.value = flutterCount}override fun getView(): View? {return this}override fun dispose() {// 解除绑定mChannel.setMethodCallHandler(null)}}
1.1 PlatformViewFactory
Android:ComputeLayoutPlatformFactory.kt
package com.example.flutter_mix_android.ui.flutterplugin.factoryimport android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.platform.ComputeLayoutPlatform
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory/*** 通过PlatformView工厂,创建PlatformView*/
class ComputeLayoutPlatformFactory(private val rootContext: Context,private val messenger: BinaryMessenger, // 二进制信使
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { // 消息编解码器private lateinit var computeLayoutPlatform: ComputeLayoutPlatformoverride fun create(context: Context, viewId: Int, args: Any?): PlatformView {computeLayoutPlatform = ComputeLayoutPlatform(context, rootContext, messenger, viewId, args)return computeLayoutPlatform}}
1.2 FlutterPlugin
Android:FlutterPlugin.kt
package com.example.flutter_mix_android.ui.flutterplugin.plugin;import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.factory.ComputeLayoutPlatformFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin/*** 将AndroidView 注册为 Flutter插件** rootContext:这个context,我是用来作ViewModel观察的,setLifecycleOwner*/
class ComputeLayoutPlugin(private val rootContext: Context) : FlutterPlugin {companion object {// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样private const val viewType: String = "com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform"}/*** 连接到flutter引擎时调用*/override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {// 将Android原生View 在Flutter引擎上注册binding.platformViewRegistry.registerViewFactory(viewType,ComputeLayoutPlatformFactory(rootContext, binding.binaryMessenger))}/*** 与flutter引擎分离时调用*/override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}}
1.3 注册插件
Android:MainActivity.kt
Ps:建议大家直接使用FlutterFragmentActivity平替掉FlutterActivity,因为
FlutterActivity继承于Activity;
FlutterFragmentActivity继承于FragmentActivity,它实现了 LifecycleOwner 和 ViewModelStoreOwner;
package com.example.flutter_mix_android.ui.activityimport com.example.flutter_mix_android.ui.flutterplugin.plugin.ComputeLayoutPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngineclass MainActivity: FlutterFragmentActivity() {override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 注册为Flutter插件flutterEngine.plugins.add(ComputeLayoutPlugin(this))}}
1.4 实体 + LiveData
package com.example.flutter_mix_android.beanimport androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass CountBean : ViewModel() {var curNum: MutableLiveData<Int> = MutableLiveData<Int>() // Android端点击次数var flutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(接收到的)var getFlutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(主动获取的)}
2、Flutter端
1.0 页面完整代码
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mix_android/bean/count_bean.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',debugShowCheckedModeBanner: false,theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),useMaterial3: true,),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {final CountBean countBean = CountBean();late MethodChannel channel;// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样final String viewType = 'com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform';static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 发送数据static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 获取 Android端 数据static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 获取 Flutter端 数据/// 初始化消息通道initChannel(int viewId) {channel = MethodChannel('flutter.mix.android/compute/$viewId'); // 创建 Flutter端和Android端的,相互通信的通道// 监听来自 Android端 的消息通道// Android端调用了函数,这个handler函数就会被触发channel.setMethodCallHandler(handler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发Future<dynamic> handler(MethodCall call) async {// 获取调用函数的名称final String methodName = call.method;switch (methodName) {case ANDROID_SEND_FLUTTER_DATA_NOTICE:{int androidCount = call.arguments['androidNum'];countBean.androidNum.value = androidCount;return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success';}case ANDROID_GET_FLUTTER_DATA_NOTICE:{return countBean.curNum.value ?? 0;}default:{return PlatformException(code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述');}}}/// Flutter端 向 Android端 发送数据,PUT 操作flutterSendAndroidData() {Map<String, int> map = {'flutterNum': countBean.curNum.value};channel.invokeMethod(FLUTTER_SEND_ANDROID_DATA_NOTICE, map).then((value) {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Result:$value');}).catchError((e) {if (e is MissingPluginException) {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');} else {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');}});}/// Flutter端 获取 Android端 数据,GET 操作flutterGetAndroidData() {channel.invokeMethod(FLUTTER_GET_ANDROID_DATA_NOTICE).then((value) {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Result:$value');countBean.getAndroidNum.value = value ?? 0;}).catchError((e) {if (e is MissingPluginException) {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');} else {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');}});}/// 累计点击次数computeCount() {countBean.curNum.value += 1;}Widget computeWidget() {final ButtonStyle btnStyle = ElevatedButton.styleFrom(elevation: 0,padding: const EdgeInsets.symmetric(horizontal: 12),backgroundColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(35)));return Padding(padding: const EdgeInsets.all(16),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('Flutter页面',style: TextStyle(color: Color(0xff0066ff),fontSize: 20,fontWeight: FontWeight.bold),),Padding(padding: const EdgeInsets.only(top: 16, bottom: 8),child: Row(children: [ValueListenableBuilder<int>(valueListenable: countBean.curNum,builder: (context, count, _) {return Text('点击次数:$count',style: const TextStyle(fontSize: 16));}),Padding(padding: const EdgeInsets.only(left: 16, right: 8),child: ElevatedButton(style: btnStyle,onPressed: computeCount,child: const Text('+1'),),),ElevatedButton(style: btnStyle,onPressed: flutterSendAndroidData,child: const Text('发送给Android端'),)],),),Padding(padding: const EdgeInsets.only(bottom: 8),child: Row(children: [ValueListenableBuilder(valueListenable: countBean.getAndroidNum,builder: (context, count, _) {return Text('获取Android页面点击次数:$count',style: const TextStyle(fontSize: 16));}),Padding(padding: const EdgeInsets.only(left: 16, right: 3),child: ElevatedButton(style: btnStyle,onPressed: flutterGetAndroidData,child: const Text('获取Android端数据'),),),],),),ValueListenableBuilder(valueListenable: countBean.androidNum,builder: (context, count, _) {return Text('接收Android端发送的点击次数:$count',style: const TextStyle(fontSize: 16));}),],),);}@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: const Color(0xffA4D3EE),body: SizedBox(width: MediaQuery.of(context).size.width,height: MediaQuery.of(context).size.height,child: SafeArea(top: true,child: Column(children: [Expanded(flex: 1,child: AndroidView(viewType: viewType, // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样creationParams: {'flutterNum': countBean.curNum.value}, // Flutter端 初始化时 向Android端 传递的参数creationParamsCodec: const StandardMessageCodec(), // 消息编解码器onPlatformViewCreated: (viewId) {initChannel(viewId);// 使用 viewId 构建不同名称的 MethodChannel,// 主要应用于 多个相同AndroidView一起使用时,避免消息冲突// List<MethodChannel> mChannels = [];// mChannels.add(MethodChannel('flutter.mix.android/compute/$viewId'));// mChannels[0].invokeMethod(method)// mChannels[0].setMethodCallHandler((call) => null)},)),Expanded(flex: 1, child: computeWidget()),],),),),);}}
1.1 实体 + ValueNotifier
import 'package:flutter/cupertino.dart';class CountBean {ValueNotifier<int> curNum = ValueNotifier<int>(10); // Flutter端点击次数ValueNotifier<int> androidNum = ValueNotifier<int>(0); // Android端点击次数(接收到的)ValueNotifier<int> getAndroidNum = ValueNotifier<int>(0); // Android端点击次数(主动获取的)}
6、源码地址
https://github.com/LanSeLianMa/flutter_mix_android