跳到主内容

将 Flutter 视图添加到 Android 应用

了解如何通过 Flutter Views 执行高级集成。

与之前介绍的 FlutterActivity 和 FlutterFragment 相比,通过 FlutterView 进行集成需要更多的工作。

从根本上讲,Dart 端的 Flutter 框架需要访问各种 Activity 级别的事件和生命周期才能正常运行。由于 FlutterView(它是一个 android.view.View)可以添加到开发者应用程序拥有的任何 Activity 中,且 FlutterView 本身无法访问 Activity 级别的事件,因此开发者必须手动将这些连接桥接到 FlutterEngine

你选择如何将应用程序 Activity 的事件传递给 FlutterView,取决于你的具体应用需求。

示例

#
Add Flutter View sample video

与 FlutterActivity 和 FlutterFragment 的指南不同,FlutterView 集成通过示例项目演示效果更好。

示例项目地址:https://github.com/flutter/samples/tree/main/add_to_app/android_view。该项目记录了一个简单的 FlutterView 集成,其中 FlutterView 被用作 RecycleView 卡片列表中的部分单元格(如上图 GIF 所示)。

通用方法

#

FlutterView 级别集成的核心要点是:你必须在自己的应用程序代码中,重新创建 FlutterActivityAndFragmentDelegate 中存在的 Activity、FlutterViewFlutterEngine 之间的各种交互。在使用 FlutterActivityFlutterFragment 时,这些连接会自动完成;但由于在本例中 FlutterView 是被添加到应用程序的 ActivityFragment 中,你必须手动重新创建这些连接。否则,FlutterView 将无法渲染任何内容,或者出现其他功能缺失的情况。

示例 FlutterViewEngine 类展示了一种在 ActivityFlutterViewFlutterEngine 之间进行特定于应用程序连接的实现方式。

需要实现的 API

#

要使 Flutter 能够绘制内容,绝对必要的最小实现步骤是:

FlutterViewActivity 不再可见时,也必须调用 detachFromFlutterEngine 以及 LifecycleChannel 类上的其他生命周期方法,以避免资源泄漏。

此外,请查看 FlutterViewEngine 演示类或 FlutterActivityAndFragmentDelegate 中的其余实现,以确保剪贴板、系统 UI 覆盖层、插件等其他功能正常工作。

内容尺寸视图

#

通常,FlutterView 需要通过自身尺寸或匹配父级尺寸来确定固定尺寸。这可以在示例项目中看到。不过,现在也可以让 FlutterView 根据其内容自行调整大小。通过将高度或宽度设置为 content_wrapFlutterView 可以自行调整大小,如内容尺寸示例项目所示。

  • 若要在部署应用时启用内容尺寸视图(Content-sized view),请在项目的 AndroidManifest.xml 文件中的 <application> 标签下添加以下设置:
xml
<meta-data
  android:name="io.flutter.embedding.android.EnableContentSizing"
  android:value="true" />

限制

#

由于内容尺寸的 Flutter 视图要求 Flutter 应用能够自行调整大小,因此不支持某些小部件。

  • 具有无限大小的小部件,如 ListView
  • 将其大小推迟给子级的小部件,如 LayoutBuilder

在实践中,这意味着许多常见的小部件都不受支持,例如 ScaffoldBuilderCupertinoTimerPicker,或任何内部依赖 LayoutBuilder 的小部件。如有疑问,可以使用 UnconstrainedBox 来测试小部件在内容尺寸视图中的可用性,如下例所示:

dart
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context)
  => MaterialApp(home: MyPage());
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: UnconstrainedBox(
          // TODO: Edit this line to check if a widget
          // can cause problems with content-sized views.
          child: Text('This works!'),
          // child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
          // child: ListView(children: [Text('This blows up!')]),
        )
    );
  }
}