Platform views(平台視圖)允許你在 Flutter 應用程式中嵌入原生視圖,因此你可以從 Dart 端對原生視圖進行變換、裁剪和透明度等操作。

這讓你,例如,可以直接在 Flutter 應用程式內使用原生網頁視圖。

macOS 採用 Hybrid composition(混合式組成),這表示 原生的 NSView 會被加入到視圖階層中。

要在 macOS 上建立 platform view,請依照以下步驟操作:

Dart 端操作

#

在 Dart 端,建立一個 Widget 並加入 build 實作, 如以下步驟所示:

在 Dart 元件(Widget)檔案中,進行如下 native_view_example.dart 所示的修改:

  1. 加入以下 import:

    dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/services.dart';
  2. 實作一個 build() 方法:

    dart
    Widget build(BuildContext context) {
      // This is used in the platform side to register the view.
      const String viewType = '<platform-view-type>';
      // Pass parameters to the platform side.
      final Map<String, dynamic> creationParams = <String, dynamic>{};
    
      return AppKitView(
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    }

如需更多資訊,請參閱 AppKitView API 文件。

在平台端

#

實作 factory 以及 platform view(平台視圖)。 NativeViewFactory 會建立 platform view,而 platform view 則會提供 NSView 的參考。 例如,NativeView.swift

NativeView.swift
swift
import Cocoa
import FlutterMacOS

class NativeViewFactory: NSObject, FlutterPlatformViewFactory {
  private var messenger: FlutterBinaryMessenger

  init(messenger: FlutterBinaryMessenger) {
    self.messenger = messenger
    super.init()
  }

  func create(
    withViewIdentifier viewId: Int64,
    arguments args: Any?
  ) -> NSView {
    return NativeView(
      viewIdentifier: viewId,
      arguments: args,
      binaryMessenger: messenger)
  }

  /// Implementing this method is only necessary when
  /// the `arguments` in `createWithFrame` is not `nil`.
  public func createArgsCodec() -> (FlutterMessageCodec & NSObjectProtocol)? {
    return FlutterStandardMessageCodec.sharedInstance()
  }
}

class NativeView: NSView {

  init(
    viewIdentifier viewId: Int64,
    arguments args: Any?,
    binaryMessenger messenger: FlutterBinaryMessenger?
  ) {
    super.init(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    wantsLayer = true
    layer?.backgroundColor = NSColor.systemBlue.cgColor
    // macOS views can be created here
    createNativeView(view: self)
  }

    required init?(coder nsCoder: NSCoder) {
        super.init(coder: nsCoder)
    }

  func createNativeView(view _view: NSView) {
    let nativeLabel = NSTextField()
    nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
    nativeLabel.stringValue = "Native text from macOS"
    nativeLabel.textColor = NSColor.black
    nativeLabel.font = NSFont.systemFont(ofSize: 14)
    nativeLabel.isBezeled = false
    nativeLabel.focusRingType = .none
    nativeLabel.isEditable = true
    nativeLabel.sizeToFit()
    _view.addSubview(nativeLabel)
  }
}

最後,註冊 platform view(平台視圖)。 這可以在應用程式或外掛(plugin)中完成。

若要在應用程式中註冊,請修改 App 的 MainFlutterWindow.swift

MainFlutterWindow.swift
swift
import Cocoa
import FlutterMacOS

class MainFlutterWindow: NSWindow {
  override func awakeFromNib() {
    // ...

    let registrar = flutterViewController.registrar(forPlugin: "plugin-name")
    let factory = NativeViewFactory(messenger: registrar.messenger)
    registrar.register(
      factory,
      withId: "<platform-view-type>")
  }
}

若要註冊 plugin,請修改該 plugin 的主檔案:

Plugin.swift
swift
import Cocoa
import FlutterMacOS

public class Plugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let factory = NativeViewFactory(messenger: registrar.messenger)
    registrar.register(factory, withId: "<platform-view-type>")
  }
}

如需更多資訊,請參閱以下的 API 文件:

綜合應用

#

當你在 Dart 中實作 build() 方法時, 可以使用 defaultTargetPlatform 來偵測平台,並決定要使用哪個元件(Widget):

dart
Widget build(BuildContext context) {
  // This is used in the platform side to register the view.
  const String viewType = '<platform-view-type>';
  // Pass parameters to the platform side.
  final Map<String, dynamic> creationParams = <String, dynamic>{};

  switch (defaultTargetPlatform) {
    case TargetPlatform.android:
    // return widget on Android.
    case TargetPlatform.iOS:
    // return widget on iOS.
    case TargetPlatform.macOS:
    // return widget on macOS.
    default:
      throw UnsupportedError('Unsupported platform view');
  }
}

效能

#

在 Flutter 中使用平台視圖(Platform views)會帶來效能上的取捨。

舉例來說,在一般的 Flutter 應用程式中,Flutter UI 會在專屬的光柵執行緒(raster thread)上進行組合。這讓 Flutter 應用程式能夠保持高速,因為這個執行緒很少被阻塞。

當平台視圖以混合組合(hybrid composition)方式渲染時,Flutter UI 仍然會從專屬的光柵執行緒進行組合,但平台視圖則會在平台執行緒(platform thread)上執行圖形操作。為了將合併後的內容進行光柵化,Flutter 會在其光柵執行緒與平台執行緒之間進行同步。因此,任何在平台執行緒上的緩慢或阻塞操作,都可能對 Flutter 的圖形效能產生負面影響。