在 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 中发生动画时,你可以使用占位符纹理。换句话说,如果在渲染平台视图时动画很慢,请考虑对原生视图进行截图,并将其渲染为纹理。

更多信息,请查看下面链接: