The Flutter hybrid development family includes the following:
- Embed native View-Android
- Embed native View-ios
- Communicate with native -MethodChannel
- Communicate with native -BasicMessageChannel
- Communicate with native -EventChannel
- Add Flutter to Android Activity
- Add Flutter to Android Fragment
- Add Flutter to iOS
Share one article every weekday, welcome to follow, like and forward.
iOS View
It is recommended to use Xcode for development. Under the left Project TAB of Android Studio, select any file in the ios directory, and Open ios Module in Xcode will appear in the upper right corner.
Click to open it, as follows:
Create an iOS View in the Runner directory that inherits the FlutterPlatformView and returns a simple UILabel:
import Foundation
import Flutter
class MyFlutterView: NSObject.FlutterPlatformView { let label = UILabel() init(_ frame: CGRect,viewID: Int64,args :Any? ,messenger :FlutterBinaryMessenger) { label.text ="I'm iOS View"
}
func view() -> UIView {
return label
}
}
Copy the code
- GetView: Returns the iOS View
Registered PlatformView
Create MyFlutterViewFactory:
import Foundation
import Flutter
class 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()
}
}
Copy the code
Register in the AppDelegate:
import UIKit
import 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)
}
}
Copy the code
Remember the plugins. Flutter. IO/custom_platform_view, this string is needed in the flutter and consistent.
Embedded Flutter
Called in 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': 'Parameters that a Flutter passes to AndroidTextView'},
creationParamsCodec: StandardMessageCodec(),
);
}else if(defaultTargetPlatform == TargetPlatform.iOS){
return UiKitView(
viewType: 'plugins.flutter.io/custom_platform_view',); }}returnScaffold( appBar: AppBar(), body: Center( child: platformView(), ), ); }}Copy the code
DefaultTargetPlatform == targetPlatform. iOS
Set initialization parameters
The Flutter end is modified as follows:
UiKitView(
viewType: 'plugins.flutter.io/custom_platform_view',
creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'},
creationParamsCodec: StandardMessageCodec(),
)
Copy the code
- CreationParams: Parameter passed by the plugin to the constructor of AndroidView.
- creationParamsCodec: creationParams is encoded and sent to the platform side, which should match the codec passed to the constructor. Range of values:
- StandardMessageCodec
- JSONMessageCodec
- StringCodec
- BinaryCodec
Modify MyFlutterView:
import Foundation
import Flutter
class 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
}
}
Copy the code
End result:
The Flutter sends messages to the iOS View
Modify the Flutter end to create MethodChannel for communication:
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': 'Parameters that a Flutter passes to AndroidTextView'},
creationParamsCodec: StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/custom_platform_view',
creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'}, creationParamsCodec: StandardMessageCodec(), ); }}return Scaffold(
appBar: AppBar(),
body: Column(children: [
RaisedButton(
child: Text('Pass parameters to native View'),
onPressed: () {
platform.invokeMethod('setText', {'name': 'laomeng'.'age': 18}); }, ), Expanded(child: platformView()), ]), ); }}Copy the code
Also create a MethodChannel in the native View for communication:
import Foundation
import Flutter
class 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 = \(name), Age: \(age)"
}
}
}
}
func view() -> UIView {
return label
}
}
Copy the code
Flutter fetches messages to the Android View
Instead of sending messages, a Flutter requests data to the native, which returns data to the Flutter end, modifying MyFlutterView onMethodCall:
import Foundation
import Flutter
class 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 = \(name), Age: \(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
}
}
Copy the code
Result () is the data returned.
Data received by the Flutter end:
var _data = 'Get data';
RaisedButton(
child: Text('$_data'),
onPressed: () async {
var result = await platform
.invokeMethod('getData', {'name': 'laomeng'.'age': 18});
setState(() {
_data = '${result['name']}.${result['age']}'; }); },),Copy the code
Resolve multiple native View communication conflicts
Of course the page has three native views,
class PlatformViewDemo extends StatefulWidget {
@override
_PlatformViewDemoState createState() => _PlatformViewDemoState();
}
class _PlatformViewDemoState extends State<PlatformViewDemo> {
static const platform =
const MethodChannel('com.flutter.guide.MyFlutterView');
var _data = 'Get data';
@override
Widget build(BuildContext context) {
Widget platformView() {
if (defaultTargetPlatform == TargetPlatform.android) {
return AndroidView(
viewType: 'plugins.flutter.io/custom_platform_view',
creationParams: {'text': 'Parameters that a Flutter passes to AndroidTextView'},
creationParamsCodec: StandardMessageCodec(),
);
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
return UiKitView(
viewType: 'plugins.flutter.io/custom_platform_view',
creationParams: {'text': 'Parameters that a Flutter passes to IOSTextView'}, creationParamsCodec: StandardMessageCodec(), ); }}return Scaffold(
appBar: AppBar(),
body: Column(children: [
Row(
children: [
RaisedButton(
child: Text('Pass parameters to native 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())), ]), ); }}Copy the code
Now click the Pass parameter to the native View button which View will change the content, actually only the last one will change.
How do I change the contents of a specified View? The key is MethodChannel, just change the name of the above three channels to be different:
- The first method: Pass a unique ID through an initialization parameter to the native View, which uses this ID to build a MethodChannel with a different name.
- The second method (recommended) : when a native View is generated, the system generates a unique ID for it: viewId, which is used to build a MethodChannel with a different name.
Native Views use viewId to build methodChannels with different names:
import Foundation
import Flutter
class 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
}
}
Copy the code
The Flutter side creates a different MethodChannel for each native View:
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': 'Parameters that a Flutter passes to AndroidTextView'},
creationParamsCodec: StandardMessageCodec(),
)
Copy the code
To send a message to the first:
platforms[0]
.invokeMethod('setText', {'name': 'laomeng'.'age': 18});
Copy the code