Flutter 混合開發系列 包含如下:
嵌入原生View-Android嵌入原生View-iOS與原生通訊-MethodChannel與原生通訊-BasicMessageChannel與原生通訊-EventChannel新增 Flutter 到 Android Activity新增 Flutter 到 Android Fragment新增 Flutter 到 iOS
iOS View建議使用 Xcode 進行開發,在 Android Studio 左側 project tab下選中 ios 目錄下任意一個檔案,右上角會出現 Open iOS module in Xcode ,
在Runner 目錄下建立 iOS View,此 View 繼承 FlutterPlatformView ,返回一個簡單的 UILabel :
import Foundationimport Flutterclass MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { label.text = "我是 iOS View" } func view() -> UIView { return label } }1234567891011121314
getView :返回iOS View註冊PlatformView
建立 MyFlutterViewFactory:
import Foundationimport Flutterclass MyFlutterViewFactory: NSObject,FlutterPlatformViewFactory { var messenger:FlutterBinaryMessenger init(messenger:FlutterBinaryMessenger) { self.messenger = messenger super.init() } func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView { return MyFlutterView(frame,viewID: viewId,args: args,messenger: messenger) } func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol { return FlutterStandardMessageCodec.sharedInstance() }}1234567891011121314151617181920
在 AppDelegate 中註冊:
import UIKitimport Flutter@UIApplicationMain@objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) let registrar:FlutterPluginRegistrar = self.registrar(forPlugin: "plugins.flutter.io/custom_platform_view_plugin")! let factory = MyFlutterViewFactory(messenger: registrar.messenger()) registrar.register(factory, withId: "plugins.flutter.io/custom_platform_view") return super.application(application, didFinishLaunchingWithOptions: launchOptions) }}1234567891011121314151617
記住 plugins.flutter.io/custom_platform_view ,這個字串在 Flutter 中需要與其保持一致。
嵌入Flutter在 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', onPlatformViewCreated: (viewId) { print('viewId:$viewId'); platforms .add(MethodChannel('com.flutter.guide.MyFlutterView_$viewId')); }, creationParams: {'text': 'Flutter傳給AndroidTextView的引數'}, creationParamsCodec: StandardMessageCodec(), ); }else if(defaultTargetPlatform == TargetPlatform.iOS){ return UiKitView( viewType: 'plugins.flutter.io/custom_platform_view', ); } } return Scaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); }}12345678910111213141516171819202122232425262728
上面嵌入的是 iOS View,因此透過 defaultTargetPlatform == TargetPlatform.iOS 判斷當前平臺載入,在 iOS 上執行效果:
設定初始化引數Flutter 端修改如下:
UiKitView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給IOSTextView的引數'}, creationParamsCodec: StandardMessageCodec(), )1234
creationParams :傳遞的引數,外掛可以將此引數傳遞給 AndroidView 的建構函式。creationParamsCodec :將 creationParams 編碼後再發送給平臺側,它應該與傳遞給建構函式的編解碼器匹配。值的範圍:StandardMessageCodecJSONMessageCodecStringCodecBinaryCodec
修改 MyFlutterView :
import Foundationimport Flutterclass MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { super.init() if(args is NSDictionary){ let dict = args as! NSDictionary label.text = dict.value(forKey: "text") as! String } } func view() -> UIView { return label }}1234567891011121314151617181920
最終效果:
Flutter 向 iOS View 傳送訊息修改 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(), ); } else if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給IOSTextView的引數'}, creationParamsCodec: StandardMessageCodec(), ); } } return Scaffold( appBar: AppBar(), body: Column(children: [ RaisedButton( child: Text('傳遞引數給原生View'), onPressed: () { platform.invokeMethod('setText', {'name': 'laomeng', 'age': 18}); }, ), Expanded(child: platformView()), ]), ); }}12345678910111213141516171819202122232425262728293031323334353637383940
在 原生View 中也建立一個 MethodChannel 用於通訊:
import Foundationimport Flutterclass MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { super.init() if(args is NSDictionary){ let dict = args as! NSDictionary label.text = dict.value(forKey: "text") as! String } let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView", binaryMessenger: messenger) methodChannel.setMethodCallHandler { (call, result) in if (call.method == "setText") { if let dict = call.arguments as? Dictionary<String, Any> { let name:String = dict["name"] as? String ?? "" let age:Int = dict["age"] as? Int ?? -1 self.label.text = "hello,\(name),年齡:\(age)" } } } } func view() -> UIView { return label }}12345678910111213141516171819202122232425262728293031
Flutter 向 Android View 獲取訊息與上面傳送資訊不同的是,Flutter 向原生請求資料,原生返回資料到 Flutter 端,修改 MyFlutterView onMethodCall:
import Foundationimport Flutterclass MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { super.init() if(args is NSDictionary){ let dict = args as! NSDictionary label.text = dict.value(forKey: "text") as! String } let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView", binaryMessenger: messenger) methodChannel.setMethodCallHandler { (call, result:FlutterResult) in if (call.method == "setText") { if let dict = call.arguments as? Dictionary<String, Any> { let name:String = dict["name"] as? String ?? "" let age:Int = dict["age"] as? Int ?? -1 self.label.text = "hello,\(name),年齡:\(age)" } }else if (call.method == "getData") { if let dict = call.arguments as? Dictionary<String, Any> { let name:String = dict["name"] as? String ?? "" let age:Int = dict["age"] as? Int ?? -1 result(["name":name,"age":age]) } } } } func view() -> UIView { return label }}12345678910111213141516171819202122232425262728293031323334353637
result() 是返回的資料。
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']}'; }); },),1234567891011
解決多個原生View通訊衝突問題
當然頁面有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(), ); } else if (defaultTargetPlatform == TargetPlatform.iOS) { return UiKitView( viewType: 'plugins.flutter.io/custom_platform_view', creationParams: {'text': 'Flutter傳給IOSTextView的引數'}, 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())), ]), ); }}1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
此時點選 傳遞引數給原生View 按鈕哪個View會改變內容,實際上只有最後一個會改變。
如何改變指定View的內容?重點是 MethodChannel,只需修改上面3個通道的名稱不相同即可:
第一種方法:將一個唯一 id 透過初始化引數傳遞給原生 View,原生 View使用這個id 構建不同名稱的 MethodChannel。第二種方法(推薦):原生 View 生成時,系統會為其生成唯一id:viewId,使用 viewId 構建不同名稱的 MethodChannel。原生 View 使用 viewId 構建不同名稱的 MethodChannel:
import Foundationimport Flutterclass MyFlutterView: NSObject,FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any?,messenger :FlutterBinaryMessenger) { super.init() if(args is NSDictionary){ let dict = args as! NSDictionary label.text = dict.value(forKey: "text") as! String } let methodChannel = FlutterMethodChannel(name: "com.flutter.guide.MyFlutterView_\(viewID)", binaryMessenger: messenger) methodChannel.setMethodCallHandler { (call, result:FlutterResult) in ... } } func view() -> UIView { return label }}12345678910111213141516171819202122232425
Flutter 端為每一個原生 View 建立不同的MethodChannel:
var platforms = [];UiKitView( 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(),)1234567891011
給第一個傳送訊息:
platforms[0] .invokeMethod('setText', {'name': 'laomeng', 'age': 18});1
原文連結:https://mengqd.blog.csdn.net/article/details/109150821