前言
文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章
Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客
数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。
效果图
图解
1、Android原生端
1.0 PlatformView
Android:ComputeLayoutPlatform.kt
Kotlin
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<String, Int> = mutableMapOf<String, Int>()
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<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.method
when (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
Kotlin
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
Kotlin
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;
Kotlin
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
Kotlin
package com.example.flutter_mix_android.bean
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class 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 页面完整代码
Dart
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
State<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));
}),
],
),
);
}
@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<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
Dart
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端点击次数(主动获取的)
}