前言
文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章
Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客
数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。
当前案例 Flutter SDK版本:3.13.2
效果图
图解
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: MethodChannel private lateinit var bind: LayoutComputeBinding private lateinit var viewModel: CountBean companion 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 ?: 0 viewModel.curNum.value = count + 1 } bind.androidSendFlutterData.setOnClickListener { androidSendFlutterData() } bind.androidGetFlutterData.setOnClickListener { androidGetFlutterData() } } /** * Android端 向 Flutter端 发送数据,PUT 操作 */ private fun androidSendFlutterData() { val map: MutableMap= mutableMapOf () map["androidNum"] = viewModel.curNum.value ?: 0 mChannel.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 FlutterFragmentActivity viewModel = ViewModelProvider(owner)[CountBean::class.java] bind.countBean = viewModel bind.lifecycleOwner = owner // 获取初始化时 Flutter端 向 Android 传递的参数 val map: Map = args as Map viewModel.getFlutterNum.value = map["flutterNum"] } /** * 监听来自 Flutter端 的消息通道 * * call: Android端 接收到 Flutter端 发来的 数据对象 * result:Android端 给 Flutter端 执行回调的接口对象 */ override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { // 获取调用函数的名称 val methodName: String = call.method when (methodName) { FLUTTER_SEND_ANDROID_DATA_NOTICE -> { // 回调结果对象 // 获取Flutter端传过来的数据 val flutterCount: Int? = call.argument ("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() {} }
1.1 PlatformViewFactory
Android:ComputeLayoutPlatformFactory.kt
package com.example.flutter_mix_android.ui.flutterplugin.factory import 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: ComputeLayoutPlatform override 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.activity import com.example.flutter_mix_android.ui.flutterplugin.plugin.ComputeLayoutPlugin import io.flutter.embedding.android.FlutterFragmentActivity import io.flutter.embedding.engine.FlutterEngine class 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.bean import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class CountBean : ViewModel() { var curNum: MutableLiveData= MutableLiveData () // Android端点击次数 var flutterNum: MutableLiveData = MutableLiveData () // Flutter端点击次数(接收到的) var getFlutterNum: MutableLiveData = MutableLiveData () // 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}); @override Widget 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; @override StatecreateState() => _MyHomePageState(); } class _MyHomePageState extends State { 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 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 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 ( 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)); }), ], ), ); } @override Widget 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 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 { ValueNotifiercurNum = ValueNotifier (10); // Flutter端点击次数 ValueNotifier androidNum = ValueNotifier (0); // Android端点击次数(接收到的) ValueNotifier getAndroidNum = ValueNotifier (0); // Android端点击次数(主动获取的) }
6、源码地址
https://github.com/LanSeLianMa/flutter_mix_android