Flutter 混合開發系列 包含以下:java
- 嵌入原生View-Android
- 嵌入原生View-IOS
- 與原生通訊-MethodChannel
- 與原生通訊-BasicMessageChannel
- 與原生通訊-EventChannel
- 添加 Flutter 到 Android Activity
- 添加 Flutter 到 Android Fragment
- 添加 Flutter 到 iOS
每一個工做日分享一篇,歡迎關注、點贊及轉發。android
建議使用 Android Studio 進行開發,在 Android Studio 左側 project tab下選中 android 目錄下任意一個文件,右上角會出現 Open for Editing in Android Studio ,git
點擊便可打開,打開後 project tab 並非一個 Android 項目,而是項目中全部 Android 項目,包含第三方:微信
app 目錄是當前項目的 android 目錄,其餘則是第三方的 android 目錄。app
在App 項目的 java/包名 目錄下建立嵌入 Flutter 中的 Android View,此 View 繼承 PlatformView :less
class MyFlutterView(context: Context) : PlatformView { override fun getView(): View { TODO("Not yet implemented") } override fun dispose() { TODO("Not yet implemented") } }
返回一個簡單的 TextView :async
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView { val textView: TextView = TextView(context) init { textView.text = "我是Android View" } override fun getView(): View { return textView } override fun dispose() { TODO("Not yet implemented") } }
建立PlatformViewFactory:ide
class MyFlutterViewFactory(val messenger: BinaryMessenger) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { override fun create(context: Context, viewId: Int, args: Any?): PlatformView { val flutterView = MyFlutterView(context, messenger, viewId, args as Map<String, Any>?) return flutterView } }
建立 MyPlugin :函數
class MyPlugin : FlutterPlugin { override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { val messenger: BinaryMessenger = binding.binaryMessenger binding .platformViewRegistry .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(messenger)) } companion object { @JvmStatic fun registerWith(registrar: PluginRegistry.Registrar) { registrar .platformViewRegistry() .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(registrar.messenger())) } } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { } }
記住 plugins.flutter.io/custom_platform_view ,這個字符串在 Flutter 中須要與其保持一致。post
在 App 中 MainActivity 中註冊:
class MainActivity : FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine.plugins.add(MyPlugin()) } }
若是是 Flutter Plugin,沒有MainActivity,則在對應的 Plugin onAttachedToEngine 和 registerWith 方法修改以下:
public class CustomPlatformViewPlugin : FlutterPlugin,MethodCallHandler { /// The MethodChannel that will the communication between Flutter and native Android /// /// This local reference serves to register the plugin with the Flutter Engine and unregister it /// when the Flutter Engine is detached from the Activity private lateinit var channel: MethodChannel override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "custom_platform_view") channel.setMethodCallHandler(this) val messenger: BinaryMessenger = flutterPluginBinding.binaryMessenger flutterPluginBinding .platformViewRegistry .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(messenger)) } // This static function is optional and equivalent to onAttachedToEngine. It supports the old // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting // plugin registration via this function while apps migrate to use the new Android APIs // post-flutter-1.12 via https://flutter.dev/go/android-project-migration. // // It is encouraged to share logic between onAttachedToEngine and registerWith to keep // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called // depending on the user's project. onAttachedToEngine or registerWith must both be defined // in the same class. companion object { @JvmStatic fun registerWith(registrar: Registrar) { val channel = MethodChannel(registrar.messenger(), "custom_platform_view") channel.setMethodCallHandler(CustomPlatformViewPlugin()) registrar .platformViewRegistry() .registerViewFactory( "plugins.flutter.io/custom_platform_view", MyFlutterViewFactory(registrar.messenger())) } } override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { if (call.method == "getPlatformVersion") { result.success("Android ${android.os.Build.VERSION.RELEASE}") } else { result.notImplemented() } } override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } }
在 Flutter 中調用
class PlatformViewDemo extends StatelessWidget { @override Widget build(BuildContext context) { Widget platformView(){ if(defaultTargetPlatform == TargetPlatform.android){ return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', ); } } return Scaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); } }
上面嵌入的是 Android View,所以經過 defaultTargetPlatform == TargetPlatform.android 判斷當前平臺加載,在 Android 上運行效果:
Flutter 端修改以下:
AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給AndroidTextView的參數'}, creationParamsCodec: StandardMessageCodec(), )
修改 MyFlutterView :
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView { val textView: TextView = TextView(context) init { args?.also { textView.text = it["text"] as String } } override fun getView(): View { return textView } override fun dispose() { TODO("Not yet implemented") } }
最終效果:
修改 Flutter 端,建立 MethodChannel 用於通訊:
class PlatformViewDemo extends StatefulWidget { @override _PlatformViewDemoState createState() => _PlatformViewDemoState(); } class _PlatformViewDemoState extends State<PlatformViewDemo> { static const platform = const MethodChannel('com.flutter.guide.MyFlutterView'); @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給AndroidTextView的參數'}, creationParamsCodec: StandardMessageCodec(), ); } } return Scaffold( appBar: AppBar(), body: Column(children: [ RaisedButton( child: Text('傳遞參數給原生View'), onPressed: () { platform.invokeMethod('setText', {'name': 'laomeng', 'age': 18}); }, ), Expanded(child: platformView()), ]), ); } }
在 原生View 中也建立一個 MethodChannel 用於通訊:
class MyFlutterView(context: Context, messenger: BinaryMessenger, viewId: Int, args: Map<String, Any>?) : PlatformView, MethodChannel.MethodCallHandler { val textView: TextView = TextView(context) private var methodChannel: MethodChannel init { args?.also { textView.text = it["text"] as String } 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() } } }
與上面發送信息不一樣的是,Flutter 向原生請求數據,原生返回數據到 Flutter 端,修改 MyFlutterView onMethodCall:
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 if (call.method == "getData") { val name = call.argument("name") as String? val age = call.argument("age") as Int? var map = mapOf("name" to "hello,$name", "age" to "$age" ) result.success(map) } else { result.notImplemented() } }
result.success(map) 是返回的數據。
Flutter 端接收數據:
var _data = '獲取數據'; RaisedButton( child: Text('$_data'), onPressed: () async { var result = await platform .invokeMethod('getData', {'name': 'laomeng', 'age': 18}); setState(() { _data = '${result['name']},${result['age']}'; }); }, ),
固然頁面有3個原生View,
class PlatformViewDemo extends StatefulWidget { @override _PlatformViewDemoState createState() => _PlatformViewDemoState(); } class _PlatformViewDemoState extends State<PlatformViewDemo> { static const platform = const MethodChannel('com.flutter.guide.MyFlutterView'); var _data = '獲取數據'; @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給AndroidTextView的參數'}, creationParamsCodec: StandardMessageCodec(), ); } } return Scaffold( appBar: AppBar(), body: Column(children: [ Row( children: [ RaisedButton( child: Text('傳遞參數給原生View'), onPressed: () { platform .invokeMethod('setText', {'name': 'laomeng', 'age': 18}); }, ), RaisedButton( child: Text('$_data'), onPressed: () async { var result = await platform .invokeMethod('getData', {'name': 'laomeng', 'age': 18}); setState(() { _data = '${result['name']},${result['age']}'; }); }, ), ], ), Expanded(child: Container(color: Colors.red, child: platformView())), Expanded(child: Container(color: Colors.blue, child: platformView())), Expanded(child: Container(color: Colors.yellow, child: platformView())), ]), ); } }
此時點擊 傳遞參數給原生View 按鈕哪一個View會改變內容,實際上只有最後一個會改變。
如何改變指定View的內容?重點是 MethodChannel,只需修改上面3個通道的名稱不相同便可:
原生 View 使用 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: MethodChannel init { 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});
老孟Flutter博客(330個控件用法+實戰入門系列文章):http://laomengit.com
歡迎加入Flutter交流羣(微信:laomengit)、關注公衆號【老孟Flutter】:
![]() |
![]() |