平臺檢視允許你在 Flutter 應用中嵌入原生檢視,這樣你就可以從 Dart 應用轉換、裁剪和調整原生檢視的不透明度。

例如,這允許您直接在 Flutter 應用中使用 Android SDK 中的原生 Google 地圖。

Android 上的 Platform Views 有兩種實現方式。它們在效能和保真度方面都有權衡。Platform views 要求 Android API 23+。

Platform Views 按正常方式渲染。Flutter 內容被渲染到一個紋理中。SurfaceFlinger 會組合 Flutter 內容和平臺檢視。

  • + 最佳的 Android 檢視效能和保真度。
  • - Flutter 效能會受影響。
  • - 應用的 FPS 會較低。
  • - 應用於 Flutter 控制元件的某些變換將無法應用於平臺檢視。

紋理圖層(或紋理圖層混合組合)

#

Platform Views 被渲染成一個紋理。Flutter 透過紋理繪製平臺檢視。Flutter 內容直接被渲染到一個 Surface 中。

  • + Android 檢視的良好效能。
  • + Flutter 渲染的最佳效能。
  • + 所有變換都能正確工作。
  • - 快速滾動(例如網頁檢視)會卡頓。
  • - SurfaceViews 在此模式下存在問題,將被移到一個虛擬顯示器中(破壞可訪問性)。
  • - 文字放大鏡會失效,除非 Flutter 被渲染到一個 TextureView 中。

要在 Android 上建立平臺檢視,請執行以下步驟:

在 Dart 側

#

在 Dart 端,建立一個 Widget 並新增以下構建實現之一。

混合組合

#

在您的 Dart 檔案中,例如 native_view_example.dart,請遵循以下說明:

  1. 新增以下匯入:

    dart
    import 'package:flutter/foundation.dart';
    import 'package:flutter/gestures.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter/rendering.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.
      const Map<String, dynamic> creationParams = <String, dynamic>{};
    
      return PlatformViewLink(
        viewType: viewType,
        surfaceFactory: (context, controller) {
          return AndroidViewSurface(
            controller: controller as AndroidViewController,
            gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
          );
        },
        onCreatePlatformView: (params) {
          return PlatformViewsService.initSurfaceAndroidView(
              id: params.id,
              viewType: viewType,
              layoutDirection: TextDirection.ltr,
              creationParams: creationParams,
              creationParamsCodec: const StandardMessageCodec(),
              onFocus: () {
                params.onFocusChanged(true);
              },
            )
            ..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
            ..create();
        },
      );
    }

有關更多資訊,請參閱 API 文件:

TextureLayerHybridComposition

#

在您的 Dart 檔案中,例如 native_view_example.dart,請遵循以下說明:

  1. 新增以下匯入:

    dart
    import 'package:flutter/material.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 AndroidView(
        viewType: viewType,
        layoutDirection: TextDirection.ltr,
        creationParams: creationParams,
        creationParamsCodec: const StandardMessageCodec(),
      );
    }

有關更多資訊,請參閱 API 文件:

在平臺側

#

在平臺端,使用 Kotlin 或 Java 中的標準 io.flutter.plugin.platform 包。

在您的原生程式碼中,實現以下內容:

繼承 io.flutter.plugin.platform.PlatformView 以提供對 android.view.View 的引用(例如,NativeView.kt)。

kotlin
package dev.flutter.example

import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView

internal class NativeView(context: Context, id: Int, creationParams: Map<string?, any?="">?) : PlatformView {
    private val textView: TextView

    override fun getView(): View {
        return textView
    }

    override fun dispose() {}

    init {
        textView = TextView(context)
        textView.textSize = 72f
        textView.setBackgroundColor(Color.rgb(255, 255, 255))
        textView.text = "Rendered on a native Android view (id: $id)"
    }
}

建立一個工廠類,用於建立之前建立的 NativeView 的例項(例如,NativeViewFactory.kt)。

kotlin
package dev.flutter.example

import android.content.Context
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory

class NativeViewFactory : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
    override fun create(context: Context, viewId: Int, args: Any?): PlatformView {
        val creationParams = args as Map<string?, any?="">?
        return NativeView(context, viewId, creationParams)
    }
}

最後,註冊平臺檢視。您可以在應用或外掛中完成此操作。

對於應用註冊,請修改應用的主 Activity(例如,MainActivity.kt)。

kotlin
package dev.flutter.example

import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine

class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        flutterEngine
                .platformViewsController
                .registry
                .registerViewFactory("<platform-view-type>", 
                                      NativeViewFactory())
    }
}

對於外掛註冊,請修改外掛的主類(例如,PlatformViewPlugin.kt)。

kotlin
package dev.flutter.plugin.example

import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding

class PlatformViewPlugin : FlutterPlugin {
    override fun onAttachedToEngine(binding: FlutterPluginBinding) {
        binding
                .platformViewRegistry
                .registerViewFactory("<platform-view-type>", NativeViewFactory())
    }

    override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}

</string?,></string?,>

在您的原生程式碼中,實現以下內容:

繼承 io.flutter.plugin.platform.PlatformView 以提供對 android.view.View 的引用(例如,NativeView.java)。

java
package dev.flutter.example;

import android.content.Context;
import android.graphics.Color;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.plugin.platform.PlatformView;
import java.util.Map;

class NativeView implements PlatformView {
   @NonNull private final TextView textView;

    NativeView(@NonNull Context context, int id, @Nullable Map<string, object=""> creationParams) {
        textView = new TextView(context);
        textView.setTextSize(72);
        textView.setBackgroundColor(Color.rgb(255, 255, 255));
        textView.setText("Rendered on a native Android view (id: " + id + ")");
    }

    @NonNull
    @Override
    public View getView() {
        return textView;
    }

    @Override
    public void dispose() {}
}

建立一個工廠類,用於建立之前建立的 NativeView 的例項(例如,NativeViewFactory.java)。

java
package dev.flutter.example;

import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.NonNull;
import io.flutter.plugin.common.StandardMessageCodec;
import io.flutter.plugin.platform.PlatformView;
import io.flutter.plugin.platform.PlatformViewFactory;
import java.util.Map;

class NativeViewFactory extends PlatformViewFactory {

  NativeViewFactory() {
    super(StandardMessageCodec.INSTANCE);
  }

  @NonNull
  @Override
  public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
    final Map<string, object=""> creationParams = (Map<string, object="">) args;
    return new NativeView(context, id, creationParams);
  }
}

最後,註冊平臺檢視。您可以在應用或外掛中完成此操作。

對於應用註冊,請修改應用的主 Activity(例如,MainActivity.java)。

java
package dev.flutter.example;

import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;

public class MainActivity extends FlutterActivity {
    @Override
    public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        flutterEngine
            .getPlatformViewsController()
            .getRegistry()
            .registerViewFactory("<platform-view-type>", new NativeViewFactory());
    }
}

對於外掛註冊,請修改外掛的主檔案(例如,PlatformViewPlugin.java)。

java
package dev.flutter.plugin.example;

import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;

public class PlatformViewPlugin implements FlutterPlugin {
  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) {
    binding
        .getPlatformViewRegistry()
        .registerViewFactory("<platform-view-type>", new NativeViewFactory());
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {}
}

</string,></string,></string,>

有關更多資訊,請參閱 API 文件:

最後,修改您的 build.gradle 檔案,以要求其中一個最低 Android SDK 版本。

kotlin
android {
    defaultConfig {
        minSdk = 19 // if using hybrid composition
        minSdk = 20 // if using virtual display.
    }
}

Surface Views

#

處理 SurfaceViews 對於 Flutter 來說很麻煩,應儘量避免。

手動檢視無效化

#

某些 Android 檢視在內容更改時不會自行無效化。一些示例檢視包括 SurfaceViewSurfaceTexture。當您的平臺檢視包含這些檢視時,您需要在它們被繪製之後(或者更具體地說:交換鏈翻轉之後)手動無效化檢視。手動檢視無效化是透過呼叫檢視或其父檢視之一的 invalidate 來完成的。

問題

#

現有 Platform View 問題

效能

#

Flutter 中的平臺檢視會帶來效能上的權衡。

例如,在典型的 Flutter 應用中,Flutter UI 在一個專用的柵格執行緒上組合。這使得 Flutter 應用能夠快速執行,因為主平臺執行緒很少被阻塞。

在平臺檢視使用混合組合進行渲染時,Flutter UI 是從平臺執行緒組合的,該執行緒需要與其他任務(如處理 OS 或外掛訊息)競爭。

在 Android 10 之前,混合組合會將每個 Flutter 幀從圖形記憶體複製到主記憶體,然後再複製回 GPU 紋理。由於此複製操作發生在每一幀,因此可能會影響整個 Flutter UI 的效能。在 Android 10 及更高版本中,圖形記憶體僅複製一次。

另一方面,虛擬顯示器使得原生檢視的每個畫素都透過額外的中間圖形緩衝區流動,這會消耗圖形記憶體和繪製效能。

對於複雜情況,有一些技術可以用來緩解這些問題。

例如,您可以在 Dart 中進行動畫時使用佔位符紋理。換句話說,如果平臺檢視正在渲染時動畫速度很慢,則可以考慮擷取原生檢視的螢幕截圖並將其渲染為紋理。

有關更多資訊,請參閱: