Flutter 页面嵌入 Android原生 View

前言

文章主要讲解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,它实现了 LifecycleOwnerViewModelStoreOwner

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/427808.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis----分页

1.什么是分页 分页&#xff08;Pagination&#xff09;是指将大量数据划分为多个页面进行展示的一种技术手段。在数据量较大的情况下&#xff0c;将所有数据一次性显示在页面上会导致加载时间过长和页面过于庞大&#xff0c;影响用户体验和系统性能。分页技术通过划分数据为多…

高质量简历模板网站,免费、免费、免费

你们在制作简历时&#xff0c;是不是基本只关注两件事&#xff1a;简历模板&#xff0c;还有基本信息的填写。 当你再次坐下来更新你的简历时&#xff0c;可能会发现自己不自觉地选择了那个“看起来最好看的模板”&#xff0c;填写基本信息&#xff0c;却没有深入思考如何使简历…

【GitHub项目推荐--这个「元宇宙编程」项目开源了】【转载】

推荐一个开源项目&#xff0c;它能帮助你沉浸式编程。这是一个基于 Linux 的 VR 桌面开源项目&#xff1a;Simula &#xff0c;这是一个运行在 Godot 之上的 Linux 虚拟现实窗口管理器. Godot&#xff1a;是一款制作游戏的软件&#xff0c;通过基于节点的架构来设计 2D 和 3D …

GIS项目实战06:超详细Node.js安装及系统环境配置

简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于 Chrome JavaScript 运行时建立的一个平台。 Node.js 是一个事件驱动 I/O 服务端 JavaScript 环境&#xff0c;基于 Google 的 V8 引擎&#xff0c;V8 引擎执行 Javascript 的速度非常快&#xff0c;性能…

鲲鹏微认证——openEuler开源操作系统迁移实践

文章目录 为什么要系统搬迁为什么选择欧拉欧拉系统迁移概述实施路径工具实战 为什么要系统搬迁 2020年12月&#xff0c;CentOs作为由开源社区免费提供的操作系统&#xff0c;宣布将对CentO58于2021年底停止服务&#xff0c;CentO57则于2024年6月底停止服务。 这将直接导致操作…

建议CSDN不要这样吃人xue馒头

程序员裁员潮&#xff1a;技术变革下的职业危机 2023年以来&#xff0c;谷歌、阿里巴巴各个科技公司都在裁员&#xff0c;程序员的日子也不好过。 讨论在技术变革下&#xff0c;裁员对于程序员的影响到底有多大&#xff0c;是非常有意义的话题&#xff0c;但是为什么要用“一…

Kubernetes/k8s之HPA,命名空间资源限制

Horizontal Pod Autoscaling:po的水平自动伸缩 这是k8s自带的模块 pod占用cpu比例达到一定的阀值&#xff0c;会触发伸缩机制。 根据cpu的阀值触发伸缩机制 replication controller 副本控制器 控制pod的副本数 deployment controller 节点控制器 部署pod hpa控制副本的数…

cmd输入python直接弹出windows应用商店

明明已经安装好了python&#xff0c;并且也确认配置好了python的环境变量&#xff0c;但是在cmd里输入python后&#xff0c;直接弹出windows商店&#xff0c;python获取界面&#xff0c;其实只需要关闭系统里的应用执行别名设置&#xff0c;最近出来的电脑系统里是自带开启了py…

初探 ret2libc

文章目录 ret2libc构建思路x86amd64第一阶段获取 libc 版本 ret2libc 这次我们又碰到新问题了&#xff0c;假如程序没有 system 函数和 /bin/sh 怎么办? system&#xff0c;/bin/sh 可以去 libc 里找&#xff0c;libc是Linux新系统下的C函数库&#xff0c;其中就会有system(…

macOS磁盘管理工具Paragon Hard Disk Manager,轻松且安全的改变磁盘分区

Paragon Hard Disk Manager mac版是Macos上一款磁盘管理工具&#xff0c;可以帮助你轻松而且安全的随意改变磁盘分区的大小和各种分区参数&#xff0c;作为mac磁盘分区工具也是游刃有余&#xff0c;同时在找回数据的时候也非常容易&#xff0c;并且不会损坏原来的数据&#xff…

《统计学习方法:李航》笔记 从原理到实现(基于python)-- 第3章 k邻近邻法

文章目录 第3章 k邻近邻法3.1 k近邻算法3.2 k近邻模型3.2.1 模型3.2.2 距离度量3.2.3 k值的选择3.2.4 分类决策规则 3.3 k近邻法的实现&#xff1a;kd树3.3.1 构造kd树3.3.2 搜索kd树 算法实现课本例3.1iris数据集scikit-learn实例kd树:构造平衡kd树算法例3.2 《统计学习方法&a…

生产力工具|卸载并重装Anaconda3

一、Anaconda3卸载 &#xff08;一&#xff09;官方方案一&#xff08;Uninstall-Anaconda3-不能删除配置文件&#xff09; 官方推荐的方案是两种&#xff0c;一种是直接在Anaconda的安装路径下&#xff0c;双击&#xff1a; &#xff08;可以在搜索栏或者使用everything里面搜…