透過 Platform Views 在 Flutter 應用中託管原生 Android 檢視
瞭解如何透過 Platform Views 在 Flutter 應用中託管原生 Android 檢視。
平臺檢視允許你在 Flutter 應用中嵌入原生檢視,這樣你就可以從 Dart 應用轉換、裁剪和調整原生檢視的不透明度。
例如,這允許您直接在 Flutter 應用中使用 Android SDK 中的原生 Google 地圖。
Android 上的 Platform Views 有多種實現方式。它們在效能和保真度方面各有權衡。
混合組合 (Hybrid composition)
#Platform Views 按原樣渲染。Flutter 內容被渲染為紋理。SurfaceFlinger 將 Flutter 內容與平臺檢視進行合成。
+Android 檢視的最佳效能和保真度。-Flutter 效能受損。-應用的 FPS 會降低。-
-應用於 Flutter 元件的某些轉換在應用於平臺檢視時將無法工作。
混合組合++ (HCPP)
#HCPP 是最新的混合組合策略,旨在解決原始混合組合模式中出現的合成效能和同步問題。它目前作為可選功能提供。
要求
#- Android API 34 或更高版本:原生事務同步功能所必需。
- Vulkan 渲染:裝置必須支援使用 Vulkan 進行渲染。
如果終端使用者裝置不滿足這些要求,Flutter 將自動回退到為該應用配置的現有平臺檢視策略。
選擇啟用
#由於 HCPP 是對平臺檢視支援方式的全域性升級,因此它透過配置而非標準 Dart 初始化方法(initAndroidView 等)來啟用。
您可以透過以下任一方法啟用 HCPP
-
命令列標誌(執行/測試):將
--enable-hcpp標誌傳遞給您的flutter run或flutter test命令bashflutter run --enable-hcpp
-
AndroidManifest.xml:在
AndroidManifest.xml的<application>塊內包含一個<meta-data>標籤xml<meta-data android:name="io.flutter.embedding.android.EnableHcpp" android:value="true" />
限制與已知問題
#- 複雜的疊加層堆疊:當以下四層發生交叉時,透明平臺檢視在佈局堆疊(Flutter 畫布 -> 平臺檢視 -> 疊加層 -> 透明平臺檢視)中將無法正確顯示。
要在 Android 上建立平臺檢視,請執行以下步驟。
紋理層 { #texturelayerhybridcomposition }
#平臺檢視被渲染為紋理。Flutter 繪製平臺檢視(使用該紋理)。Flutter 內容被直接渲染到 Surface 中。
+Android 檢視效能良好+Flutter 渲染效能最佳。+所有轉換都能正常工作。-快速滾動(例如網頁檢視)會有卡頓-
-SurfaceViews 在此模式下會有問題,並將被移動到虛擬顯示屏中(破壞無障礙功能 a11y) -除非 Flutter 被渲染到 TextureView 中,否則文字放大鏡功能將失效。
在 Dart 側
#在 Dart 端,建立一個 Widget 並新增以下構建實現之一。
混合組合 (Hybrid composition)
#在您的 Dart 檔案中(例如 native_view_example.dart),使用以下說明
-
新增以下匯入:
dartimport 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -
實現
build()方法dartWidget 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),使用以下說明
-
新增以下匯入:
dartimport 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -
實現
build()方法dartWidget 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)
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)
}
}
最後,註冊平臺檢視。您可以在應用或外掛中執行此操作。
對於應用註冊,修改應用的 MainActivity(例如,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);
}
}
最後,註冊平臺檢視。您可以在應用或外掛中執行此操作。
對於應用註冊,修改應用的 MainActivity(例如,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 {
minSdk = 19 // if using hybrid composition
minSdk = 20 // if using virtual display.
}
}
手動檢視失效 (Manual view invalidation)
#某些 Android 檢視在內容更改時不會自動失效。一些示例檢視包括 SurfaceView 和 SurfaceTexture。當您的平臺檢視包含這些檢視時,您需要在它們被繪製後(更具體地說:在交換鏈翻轉後)手動使檢視失效。手動檢視失效透過在 View 或其父檢視之一上呼叫 invalidate 來完成。
問題
#效能
#Flutter 中的平臺檢視會帶來效能上的權衡。
例如,在典型的 Flutter 應用中,Flutter UI 在專用的光柵執行緒上進行合成。這使得 Flutter 應用執行迅速,因為主平臺執行緒很少被阻塞。
當平臺檢視以混合組合方式渲染時,Flutter UI 會從平臺執行緒進行合成,該執行緒會與其他任務(如處理作業系統或外掛訊息)競爭。
在 Android 10 之前,混合組合將每一幀 Flutter 幀從圖形記憶體複製到主記憶體,然後再複製回 GPU 紋理。由於這種複製是按幀發生的,整個 Flutter UI 的效能可能會受到影響。在 Android 10 或更高版本中,圖形記憶體僅複製一次。
另一方面,虛擬顯示屏(Virtual display)會使原生檢視的每個畫素流經額外的中間圖形緩衝區,這會消耗圖形記憶體並影響繪製效能。
對於複雜情況,有一些技術可以用來緩解這些問題。
例如,您可以在 Dart 中進行動畫處理時使用佔位符紋理。換句話說,如果平臺檢視渲染時動畫較慢,請考慮擷取原生檢視的螢幕截圖並將其作為紋理進行渲染。
更多資訊,請參閱