参考
【Flutter 混合开发】嵌入原生View-Android
默认使用Android studio
和 Kotlin
基本配置
创建flutter项目
在终端执行
flutter create batterylevel
添加 Android 平台的实现
打开项目下的android/app/src/main/kotlin
下的 MainActivity.kt
文件。
我这里编辑器有提示,最好使用其推荐,这样代码会有提示
使用推荐后会新打开一个窗口,在原窗口里提示的错误也将不会再提示
MainActivity.kt
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngineclass MainActivity : FlutterActivity() {// 在flutter引擎启动时,将自定义的插件添加到flutter 引擎的插件列表中override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)flutterEngine.plugins.add(MyPlugin())}
}
在项目文件下创建MyFlutterView.kt
、MyFlutterViewFactory.kt
、MyPlugin.kt
三个文件
MyFlutterView.kt
编写的是原生安卓代码MyFlutterViewFactory.kt
一个自定义的Flutter平台视图工厂,用于创建创建Flutter平台视图并将其返回MyPlugin.kt
创建一个自定义的Flutter插件,将自定义的Flutter平台视图工厂注册到Flutter引擎中,以便在Flutter应用中使用该自定义视图。
MyFlutterView.kt
import android.content.Context
import android.view.View
import android.widget.TextView
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.platform.PlatformView// context 上下文对象
// messenger 用于消息传递,BinaryMessenger二进制消息
// viewId View 生成时会分配一个唯一 ID
// args Flutter 传递的初始化参数
class MyFlutterView(context: Context,messenger: BinaryMessenger,viewId: Int,args: Map<String, Any>?
) :PlatformView {// PlatformView代表一个平台视图,它是Flutter和原生平台之间的桥梁。// PlatformView允许在Flutter应用中嵌入原生视图,并提供了与Flutter框架进行交互的方法。// 定义一个文本视图private val textView: TextView = TextView(context)init {// 初始化文本视图textView.text = "我是Android View"}// 获取文本视图override fun getView(): View {return textView}// 销毁override fun dispose() {}
}
MyFlutterViewFactory.kt
import android.content.Context
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
class MyFlutterViewFactory(private val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE
) {// 创建PlatformViewoverride fun create(context: Context, viewId: Int, args: Any?): PlatformView {return MyFlutterView(context, messenger, viewId, args as Map<String, Any>?)}}
MyPlugin.kt
"com.example.batterylevel/custom_platform_view"
是自定义平台视图的标识符,需要保证唯一。
标识符通常由小写字母、数字和斜杠(/)组成,并且不包含空格或特殊字符。
具体如何编写没查到,参考文章应该是 按着包名/视图的描述
的格式,感觉挺好的
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.PluginRegistryclass MyPlugin : FlutterPlugin {// onAttachedToEngine 在Flutter插件与Flutter引擎绑定时被调用。// 在这个方法中,可以执行一些初始化操作,例如注册平台视图工厂、注册方法调用处理器等override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {// 消息发送器val messenger: BinaryMessenger = binding.binaryMessenger//binding.platformViewRegistry.registerViewFactory("com.example.batterylevel/custom_platform_view", MyFlutterViewFactory(messenger))}// 注册视图companion object { fun registerWith(registrar: PluginRegistry.Registrar) {registrar.platformViewRegistry().registerViewFactory("com.example.batterylevel/custom_platform_view",MyFlutterViewFactory(registrar.messenger()))}}// 用于在插件从 Flutter 引擎中分离时执行清理操作。// 当插件不再需要与 Flutter 引擎通信时,例如应用关闭或插件被移除时,onDetachedFromEngine 方法会被调用。override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}
}
基本内容编写完成后回到flutter项目的部分,进行运行
main.dart
void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});// This widget is the root of your application.Widget build(BuildContext context) {return MaterialApp(title: '嵌入原生View-Android',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),useMaterial3: true,),home: const PlatformViewDemo(),);}
}class PlatformViewDemo extends StatelessWidget {const PlatformViewDemo({super.key});Widget build(BuildContext context) {platformView() {// 如果是安卓平台,则返回一个AndroidView对象// AndroidView 是 Flutter 提供的一个小部件,用于在 Flutter 中嵌入原生 Android 视图if (defaultTargetPlatform == TargetPlatform.android) {// 标识符要保持一致return const AndroidView(viewType: 'com.example.batterylevel/custom_platform_view',);}}return Scaffold(appBar: AppBar(),body: Center(child: platformView(),),);}
}
flutter与原生之间的通信
关于通信可以看一下这篇文章:Flutter与原生如何进行通信
简单的参数传递
flutter中的修改,main.dart
class PlatformViewDemoState extends State<PlatformViewDemo> {Widget build(BuildContext context) {platformView() {// 如果是安卓平台,则返回一个AndroidView对象// AndroidView 是 Flutter 提供的一个小部件,用于在 Flutter 中嵌入原生 Android 视图if (defaultTargetPlatform == TargetPlatform.android) {// 标识符要保持一致return const AndroidView(viewType: 'com.example.batterylevel/custom_platform_view',creationParams: {'text': 'AndroidTextView122'},creationParamsCodec: StandardMessageCodec(),);}}return Scaffold(appBar: AppBar(),body: Center(child: platformView(),),);}
}
- creationParams :传递的参数,插件可以将此参数传递给 AndroidView 的构造函数。
- creationParamsCodec :将 creationParams 编码后再发送给平台侧,它应该与传递给构造函数的编解码器匹配。值的范围:
- StandardMessageCodec,这是 Flutter 默认的编解码器,用于序列化和反序列化标准数据类型,例如字符串、数字、布尔值、列表和映射
- JSONMessageCodec,使用 JSON 格式进行序列化和反序列化。它可以用于传递复杂的数据结构,例如嵌套的映射和列表。
- StringCodec,用于将字符串编码为字节流,并在需要时进行解码。它适用于只传递字符串类型的创建参数。
- BinaryCodec,用于直接传递字节流类型的创建参数,不进行额外的编码或解码
修改 MyFlutterView.kt
class MyFlutterView(context: Context,messenger: BinaryMessenger,viewId: Int,args: Map<String, Any>?
) :PlatformView {// PlatformView代表一个平台视图,它是Flutter和原生平台之间的桥梁。// PlatformView允许在Flutter应用中嵌入原生视图,并提供了与Flutter框架进行交互的方法。// 定义一个文本视图private val textView: TextView = TextView(context)init {// 初始化文本视图// 没学过Kotlin,大体意思是参数存在则赋值args?.also { map ->println(map["text"])println("参数")textView.text = map["text"] as String}}// 获取文本视图override fun getView(): View {return textView}// 销毁override fun dispose() {}
}
使用MethodChannel 通信
main.dart
class PlatformViewDemoState extends State<PlatformViewDemo> {// 用于通信的通道static const platform = MethodChannel('com.flutter.guide.MyFlutterView');Widget build(BuildContext context) {platformView() {// 如果是安卓平台,则返回一个AndroidView对象// AndroidView 是 Flutter 提供的一个小部件,用于在 Flutter 中嵌入原生 Android 视图if (defaultTargetPlatform == TargetPlatform.android) {// 标识符要保持一致return const AndroidView(viewType: 'com.example.batterylevel/custom_platform_view',creationParams: {'text': '向安卓传参数'},creationParamsCodec: StandardMessageCodec(),);}}return Scaffold(appBar: AppBar(),body: Column(children: [ElevatedButton(onPressed: () {// invokeMethod 的作用是在特定的上下文中调用指定的方法。它接受两个参数:方法名称和一个包含参数的对象platform.invokeMethod('setText', {'name': '张三', 'age': 18});},child: const Text("使用MethodChannel通信")),Expanded(child: platformView()!)],));}
}
修改 MyFlutterView.kt
class MyFlutterView(context: Context,messenger: BinaryMessenger,viewId: Int,args: Map<String, Any>?
) :PlatformView, MethodChannel.MethodCallHandler {// PlatformView代表一个平台视图,它是Flutter和原生平台之间的桥梁。// PlatformView允许在Flutter应用中嵌入原生视图,并提供了与Flutter框架进行交互的方法。// 定义一个文本视图private val textView: TextView = TextView(context)private var methodChannel: MethodChannelinit {// 初始化文本视图// 没学过Kotlin,大体意思是参数存在则赋值args?.also { map ->textView.text = map["text"] as String}// com.flutter.guide.MyFlutterView 标识保证唯一,与flutter中的保持一致methodChannel = MethodChannel(messenger, "com.flutter.guide.MyFlutterView")methodChannel.setMethodCallHandler(this)}// 获取文本视图override fun getView(): View {return textView}// 销毁override fun dispose() {methodChannel.setMethodCallHandler(null)}// 方法调用处理器override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {if (call.method == "setText") {val name = call.argument("name") as String?val age = call.argument("age") as Int?textView.text = "hello,$name,年龄:$age"} else {result.notImplemented()}}
}
注:
methodChannel = MethodChannel(messenger, "com.flutter.guide.MyFlutterView")
methodChannel.setMethodCallHandler(this)
必须放在
args?.also { map ->textView.text = map["text"] as String}
后面,好像存在执行顺序的问题。如果放在前面, textView.text = map["text"] as String
不会执行
flutter接收来自原生的消息
在flutter中使用invokeMethod
调用原生的方法。在原生中处理对应方法时,如果要向flutter中返回消息可以使用
textView.text = "hello,$name,年龄:$age"
result.success("收到了来自flutter的调用")
在flutter中正常接收即可
ElevatedButton(onPressed: () async{// invokeMethod 的作用是在特定的上下文中调用指定的方法。它接受两个参数:方法名称和一个包含参数的对象var res = await platform.invokeMethod('setText', {'name': '张三', 'age': 18});print(res);},child: const Text("使用MethodChannel通信"))
解决多个原生View通信冲突问题
这个看原文章就好了,主要是保证每一个通道都是唯一的。
原生 View 生成时,系统会为其生成唯一id:viewId,使用 viewId 构建不同名称的 MethodChannel。
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView, MethodChannel.MethodCallHandler {val textView: TextView = TextView(context)private var methodChannel: MethodChannelinit {args?.also {textView.text = it["text"] as String}methodChannel = MethodChannel(messenger, "com.flutter.guide.MyFlutterView_$viewId")methodChannel.setMethodCallHandler(this)}...
}
Flutter 端为每一个原生 View 创建不同的MethodChannel
var platforms = [];AndroidView(viewType: 'plugins.flutter.io/custom_platform_view',onPlatformViewCreated: (viewId) {print('viewId:$viewId');platforms.add(MethodChannel('com.flutter.guide.MyFlutterView_$viewId'));},creationParams: {'text': 'Flutter传给AndroidTextView的参数'},creationParamsCodec: StandardMessageCodec(),
)
给第一个发送消息:
platforms[0].invokeMethod('setText', {'name': 'laomeng', 'age': 18});