跳至主要內容

使用平台視圖在您的 Flutter 應用程式中託管原生 Android 檢視

平台視圖允許您在 Flutter 應用程式中嵌入原生視圖,因此您可以從 Dart 對原生視圖應用轉換、裁剪和不透明度。

舉例來說,這讓您可以直接在您的 Flutter 應用程式中使用 Android SDK 的原生 Google 地圖。

Android 上的平台視圖有兩種實作方式。它們在效能和保真度方面都有取捨。平台視圖需要 Android API 23 以上版本。

平台視圖會像平常一樣渲染。Flutter 內容會渲染到紋理中。SurfaceFlinger 會合成 Flutter 內容和平台視圖。

  • + Android 視圖的最佳效能和保真度。
  • - Flutter 效能會降低。
  • - 應用程式的 FPS 會較低。
  • - 某些可以應用於 Flutter 小部件的轉換在應用於平台視圖時將無法運作。

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

#

平台視圖會渲染到紋理中。Flutter 會繪製平台視圖(透過紋理)。Flutter 內容會直接渲染到 Surface 中。

  • + Android 視圖的良好效能
  • + Flutter 渲染的最佳效能。
  • + 所有轉換都可正確運作。
  • - 快速滾動(例如網頁視圖)會變得不順暢
  • - SurfaceView 在此模式下會有問題,將會被移動到虛擬顯示器中(破壞 a11y)
  • - 除非 Flutter 渲染到 TextureView 中,否則文字放大鏡將會失效。

若要在 Android 上建立平台視圖,請按照下列步驟操作

在 Dart 端

#

在 Dart 端,建立一個 Widget 並新增以下其中一種 build 實作。

混合組合

#

例如,在您的 Dart 檔案 native_view_example.dart 中,使用以下指示

  1. 新增以下 import

    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. 新增以下 import

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

最後,註冊平台視圖。您可以在應用程式或外掛程式中執行此操作。

對於應用程式註冊,請修改應用程式的主要活動(例如,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) {}
}

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

擴展 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);
  }
}

最後,註冊平台視圖。您可以在應用程式或外掛程式中執行此操作。

對於應用程式註冊,請修改應用程式的主要活動(例如,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) {}
}

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

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

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

Surface View

#

處理 SurfaceView 對於 Flutter 來說是有問題的,應盡可能避免。

手動檢視失效

#

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

問題

#

現有的平台視圖問題

效能

#

Flutter 中的平台視圖會帶來效能上的取捨。

例如,在典型的 Flutter 應用程式中,Flutter UI 是在專用的 raster 執行緒上合成的。這讓 Flutter 應用程式速度很快,因為主平台執行緒很少被阻塞。

當使用混合合成渲染平台視圖時,Flutter UI 是從平台執行緒合成的,這會與處理 OS 或外掛程式訊息等其他任務競爭。

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

另一方面,虛擬顯示器會讓原生視圖的每個像素流經額外的中間圖形緩衝區,這會消耗圖形記憶體並降低繪圖效能。

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

例如,您可以在 Dart 中進行動畫時使用佔位符紋理。換句話說,如果平台視圖渲染時動畫很慢,請考慮拍攝原生視圖的螢幕擷取畫面並將其渲染為紋理。

如需更多資訊,請參閱