在 Flutter 應用中使用整合平台檢視託管你的原生 Android 檢視

整合平台檢視(後稱為平台檢視)允許將原生檢視嵌入到 Flutter 應用中,所以你可以透過 Dart 將變換、裁剪和不透明度等效果應用到原生檢視。

例如,這使你可以透過使用平台檢視直接在 Flutter 應用內部使用 Android 和 iOS SDK 中的 Google Maps。

Flutter 支援兩種整合模式:虛擬顯示模式 (Virtual displays) 和混合整合模式 (Hybrid composition) 。

我們應根據具體情況來決定使用哪種模式。讓我們來看看:

  • 混合整合模式 會將原生的 android.view.View 附加到檢視層次結構中。因此,鍵盤處理和無障礙功能是開箱即用的。在 Android 10 之前,此模式可能會大大降低 Flutter UI 的幀吞吐量 (FPS)。有關更多訊息,請參見 效能 小節。

  • 虛擬顯示模式 會將 android.view.View 實例渲染為紋理,因此它不會嵌入到 Android Activity 的檢視層次結構中。某些平台互動(例如鍵盤處理和輔助功能)可能無法正常工作。

在 Android 上建立平台檢視需要如下的步驟:

在 Dart 中進行的處理

在 Dart 端,建立一個 Widget 然後新增如下的實現,具體如下:

混合整合模式

在 Dart 檔案中,例如 native_view_example.dart,請執行下列操作:

  1. 新增下面的匯入程式碼:

    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() 方法:

    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 文件:

虛擬顯示模式 (Virtual Display)

在 Dart 檔案中,例如 native_view_example.dart,請執行下列操作:

  1. 新增下面的匯入程式碼:

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

    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 文件:

在平台端

在平台端,使用 Java 或 Kotlin 中的標準套件 io.flutter.plugin.platform

在你的原生程式碼中,實現如下方法:

繼承 io.flutter.plugin.platform.PlatformView 以提供對 android.view.View 的引用,如 NativeView.kt 所示:

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

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):

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):

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) {}
}

在你的原生程式碼中,實現如下方法:

繼承 io.flutter.plugin.platform.PlatformView 以提供對 android.view.View 的引用,如 NativeView.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

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):

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):

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) {}
}

更多訊息,請檢視 API 文件:

最後,修改你的 build.gradle 檔案來滿足 Android SDK 最低版本的要求:

android {
    defaultConfig {
        minSdkVersion 19 // if using hybrid composition
        minSdkVersion 20 // if using virtual display.
    }
}

Manual view invalidation

Certain Android Views do not invalidate themselves when their content changes. Some example views include SurfaceView and SurfaceTexture. When your Platform View includes these views you are required to manually invalidate the view after they have been drawn to (or more specifically: after the swap chain is flipped). Manual view invalidation is done by calling invalidate on the View or one of its parent views.

效能

在 Flutter 中使用平台檢視時,效能會有所取捨。

例如,在典型的 Flutter 應用中,Flutter 的 UI 是專門在 raster 執行緒上合成的。由於平台的主線程很少被阻塞,因此 Flutter 應用程式可以快速執行。

使用混合整合模式渲染平台檢視時, Flutter UI 由平台執行緒完成,與其他執行緒一起競爭,例如:處理系統或外掛訊息等任務。

在 Android 10 之前,混合整合模式將每個 Flutter 幀從顯存中複製到主記憶體中,然後再將其複製回 GPU 紋理中。在 Android 10 或更高版本中,顯存會被複制兩次。由於每幀都會進行一次複製,因此可能會影響整個 Flutter UI 的效能。

另一方面,虛擬顯示模式使平台檢視的每個畫素流經附加的中間圖形緩衝區,這會浪費顯存和繪圖效能。

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

例如,當 Dart 中發生動畫時,你可以使用佔位符紋理。換句話說,如果在渲染平台檢視時動畫很慢,請考慮對原生檢視進行截圖,並將其渲染為紋理。

更多訊息,請檢視下面連結: