What is Platform View

To enable some existing Native controls to be directly referenced into the Flutter App, the Flutter team provides a Platform View that allows the Native View to be embedded into the Flutter Widget system, enabling Dart code to control the Native View. Platform Views include AndroidView and UIKitView

How to use Platform View

AndroidView

class AndroidView extends StatefulWidget { const AndroidView({ Key key, @required this.viewType, this.onPlatformViewCreated, this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, this.layoutDirection, this.gestureRecognizers, this.creationParams, this.creationParamsCodec, }) : assert(viewType ! = null), assert(hitTestBehavior ! = null), assert(creationParams == null || creationParamsCodec ! = null), super(key: key); /// Embed the unique identifier of the Android view final String viewType; / / / Platform View created the callback final PlatformViewCreatedCallback onPlatformViewCreated; / / / final hit during testing behavior PlatformViewHitTestBehavior hitTestBehavior; // view TextDirection final TextDirection layoutDirection; / / / used for processing time conflicts, to distribute time management related operations final Set < Factory < OneSequenceGestureRecognizer > > gestureRecognizers; /// Final Dynamic creationParams is used when constructing the Android view; If creationParams is not null, the value must not be NULL final MessageCodec<dynamic> creationParamsCodec; @override State<AndroidView> createState() => _AndroidViewState(); }Copy the code

Note that:

  • AndroidView only supports Android API 20 and above
  • Using AndroidView in Flutter has a high performance overhead and should be avoided

Open the created Flutter project with Android Studio and go to the top menu of Tools->Flutter->Open for Editing in Android Studio.Click to open an Android project containing the introduced tripartite libraries:In the main project, create an Android View to embed into Flutter. This View is derived from PlatformView:

class CustomFlutterView(private val context: Context, messenger: BinaryMessenger, private val viewId: Int, params: Map<String, Any>?) : PlatformView, MethodChannel.MethodCallHandler{ private var binding: CustomFlutterViewBinding = CustomFlutterViewBinding.inflate(LayoutInflater.from(context)) private var methodChannel: MethodChannel init { params?.also { binding.tvReceiverFlutterMsg.text = it["init"] as String } methodChannel = MethodChannel(messenger, "com.mufeng.flutter_native_view") methodChannel.setMethodCallHandler(this) } override fun getView(): View { binding.sendMsgToFlutter.setOnClickListener { methodChannel.invokeMethod("sendMsgToFlutter", mapOf("text" to "Hello, CustomFlutterView_$viewId")) } return binding.root } override fun dispose() { methodChannel.setMethodCallHandler(null) Log. E ("TAG", "free resource ")} Override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { if (call.method == "updateText") { val author = call.argument("author") as String? binding.tvReceiverFlutterMsg.text = "Hello, $author" } else { result.notImplemented() } } }Copy the code
  • GetView (): Returns the Android View to embed the Flutter hierarchy
  • Dispose: call when you release this VIew, the VIew is not available after this method is called, this method needs to clean up all object references, otherwise it will cause memory leak
  • Messenger: Used for messaging. This parameter is used when Flutter communicates with native
  • ViewId: A unique ID is assigned when the View is generated
  • Args: Initialization parameters passed by Flutter
Registered PlatformView

Create a PlatformViewFactory:

class CustomFlutterViewFactory(private val messenger: BinaryMessenger): PlatformViewFactory(StandardMessageCodec.INSTANCE){ override fun create(context: Context, viewId: Int, args: Any?) : PlatformView { return CustomFlutterView(context, messenger, viewId, args as Map<String, Any>) } }Copy the code

Create CustomPlugin:

class CustomPlugin : FlutterPlugin {

    companion object {

        const val VIEW_TYPE_ID: String = "com.mufeng.flutter_native_view/custom_platform_view"

        fun registerWith(registrar: PluginRegistry.Registrar) {
            registrar.platformViewRegistry()
                    .registerViewFactory(VIEW_TYPE_ID, CustomFlutterViewFactory(registrar.messenger()))
        }
    }

    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        val messenger: BinaryMessenger = binding.binaryMessenger
        binding.platformViewRegistry.registerViewFactory(VIEW_TYPE_ID, CustomFlutterViewFactory(messenger))
    }

    override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {

    }
}
Copy the code

Note: the string VIEW_TYPE_ID must be consistent with the Flutter end

Register with MainActivity in App:

class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        flutterEngine.plugins.add(CustomPlugin())
    }
}
Copy the code

Note that if a Flutter Plugin is developed without MainActivity, it needs to be modified using the onAttachedToEngine() and **registerWith()** methods of the corresponding Plugin class

Called in Flutter
class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('com.mufeng.flutter_native_view'); String result; @override void initState() { super.initState(); platform.setMethodCallHandler((call) { setState(() { result = call.arguments['text']; }); ${call.arguments['text']}"); return; }); } @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: "com.mufeng.flutter_native_view/custom_platform_view", creationParams: {'init': 'Initialization parameters passed from Flutter '}, creationParamsCodec: StandardMessageCodec(), onPlatformViewCreated: (viewId) {print('View created; ViewId: $viewId'); }); } } return Scaffold( appBar: AppBar( title: Text( 'Platform View', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold), ), centerTitle: true, ), body: Column( children: [ TextButton(onPressed: () { platform.invokeMethod('updateText', {'author': 'MuFeng'}); }, child: Text(' pass parameters to Native View'), Text(result??' wait for Native to send data '), Expanded(Child: platformView()),],),); }}Copy the code

To see how it works on Android:

UIKitView

class UiKitView extends StatefulWidget { const UiKitView({ Key key, @required this.viewType, this.onPlatformViewCreated, this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, this.layoutDirection, this.creationParams, this.creationParamsCodec, this.gestureRecognizers, }) : assert(viewType ! = null), assert(hitTestBehavior ! = null), assert(creationParams == null || creationParamsCodec ! = null), super(key: key); // the unique identifier embedded in the iOS view final String viewType; / / / Platform View created the callback final PlatformViewCreatedCallback onPlatformViewCreated; / / / final hit during testing behavior PlatformViewHitTestBehavior hitTestBehavior; // view TextDirection final TextDirection layoutDirection; /// Final Dynamic creationParams is used in Android view construction. If creationParams is not null, the value must not be NULL final MessageCodec<dynamic> creationParamsCodec; / / / used for processing time conflicts, to distribute time management related operations final Set < Factory < OneSequenceGestureRecognizer > > gestureRecognizers; @override State<UiKitView> createState() => _UiKitViewState(); }Copy the code

To develop a Flutter project using Xcode, Open the created Flutter project in Android Studio. At the top of Android Studio, click Tools->Flutter->Open iOS Module in Xcode to Open an iOS project

The first step is to create an iOS View in the Runner directory that inherits the FlutterPlatformView

import Foundation import Flutter class CustomFlutterView:NSObject, FlutterPlatformView { let label = UILabel() init(_ frame: CGRect, viewID: Int64, params: Any? , messenger: FlutterBinaryMessenger) { super.init() if(params is NSDictionary){ let dict = params as! NSDictionary label.text = "\(dict.value(forKey: "init") as? String ?? } let methodChannel = FlutterMethodChannel(name: "com.mufeng. Flutter_native_view ", binaryMessenger: messenger) methodChannel.setMethodCallHandler {(call, result) in if(call.method == "updateText"){ if let dict = call.arguments as? Dictionary<String, Any>{ let author: String = dict["author"] as? String ?? "" self.label.text = "Hello, \(author), AddOnClick {(view) in var arguments = Dictionary<String, Any>() arguments["text"] = "CustomFlutterView_\(viewID)" methodChannel.invokeMethod("sendMsgToFlutter", arguments: arguments) } } func view() -> UIView { return label } }Copy the code

Step 2 create CustomFlutterViewFactory:


import Foundation
import Flutter

class CustomFlutterViewFactory: NSObject, FlutterPlatformViewFactory {
    var messenger: FlutterBinaryMessenger
    
    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }
    
    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments params: Any?) -> FlutterPlatformView{
        return CustomFlutterView(frame, viewID: viewId, params: params, messenger: messenger)
    }
    
    func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
            return FlutterStandardMessageCodec.sharedInstance()
        }
}
Copy the code

Step 3: Register in the AppDelegate:

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    
  let VIEW_TYPE_ID: String = "com.mufeng.flutter_native_view/custom_platform_view"
    
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
    
    let registrar: FlutterPluginRegistrar = self.registrar(forPlugin: VIEW_TYPE_ID)!
    let factory = CustomFlutterViewFactory(messenger: registrar.messenger())
    registrar.register(factory, withId: VIEW_TYPE_ID)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}
Copy the code

Note: the string VIEW_TYPE_ID must be consistent with the Flutter end

Step 4, call in Flutter

class _MyHomePageState extends State<MyHomePage> { static const platform = const MethodChannel('com.mufeng.flutter_native_view'); String result; @override void initState() { super.initState(); platform.setMethodCallHandler((call) { setState(() { result = call.arguments['text']; }); ${call.arguments['text']}"); return; }); } @override Widget build(BuildContext context) { Widget platformView() { if (defaultTargetPlatform == TargetPlatform.android) { return AndroidView( viewType: "com.mufeng.flutter_native_view/custom_platform_view", creationParams: {'init': 'Initialization parameters passed from Flutter '}, creationParamsCodec: StandardMessageCodec(), onPlatformViewCreated: (viewId) {print('View created; ViewId: $viewId'); }); }else if(defaultTargetPlatform == TargetPlatform.android){ return UiKitView( viewType: "com.mufeng.flutter_native_view/custom_platform_view", creationParams: {'init': 'Initialization parameters passed from Flutter '}, creationParamsCodec: StandardMessageCodec(), onPlatformViewCreated: (viewId) {print('View created; ViewId: $viewId'); }); }else{return Text(' currently unsupported platform type '); } } return Scaffold( appBar: AppBar( title: Text( 'Platform View', style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold), ), centerTitle: true, ), body: Column( children: [ TextButton(onPressed: () { platform.invokeMethod('updateText', {'author': 'MuFeng'}); }, child: Text(' pass parameters to Native View'), Text(result??' wait for Native to send data '), Expanded(Child: platformView()),],),); }}Copy the code

To see how this works in iOS:

This article code address github.com/hanlin19900…

To embed Native components in a Flutter, you need to attach textures to the Flutter