将 Flutter 视图添加到 Android 应用
了解如何通过 Flutter Views 执行高级集成。
与之前介绍的 FlutterActivity 和 FlutterFragment 相比,通过 FlutterView 进行集成需要更多的工作。
从根本上讲,Dart 端的 Flutter 框架需要访问各种 Activity 级别的事件和生命周期才能正常运行。由于 FlutterView(它是一个 android.view.View)可以添加到开发者应用程序拥有的任何 Activity 中,且 FlutterView 本身无法访问 Activity 级别的事件,因此开发者必须手动将这些连接桥接到 FlutterEngine。
你选择如何将应用程序 Activity 的事件传递给 FlutterView,取决于你的具体应用需求。
示例
#
与 FlutterActivity 和 FlutterFragment 的指南不同,FlutterView 集成通过示例项目演示效果更好。
示例项目地址:https://github.com/flutter/samples/tree/main/add_to_app/android_view。该项目记录了一个简单的 FlutterView 集成,其中 FlutterView 被用作 RecycleView 卡片列表中的部分单元格(如上图 GIF 所示)。
通用方法
#FlutterView 级别集成的核心要点是:你必须在自己的应用程序代码中,重新创建 FlutterActivityAndFragmentDelegate 中存在的 Activity、FlutterView 和 FlutterEngine 之间的各种交互。在使用 FlutterActivity 或 FlutterFragment 时,这些连接会自动完成;但由于在本例中 FlutterView 是被添加到应用程序的 Activity 或 Fragment 中,你必须手动重新创建这些连接。否则,FlutterView 将无法渲染任何内容,或者出现其他功能缺失的情况。
示例 FlutterViewEngine 类展示了一种在 Activity、FlutterView 和 FlutterEngine 之间进行特定于应用程序连接的实现方式。
需要实现的 API
#要使 Flutter 能够绘制内容,绝对必要的最小实现步骤是:
- 当
FlutterView被添加到已恢复(resumed)的Activity视图层级中并可见时,调用attachToFlutterEngine;以及 - 当托管
FlutterView的Activity可见时,在FlutterEngine的lifecycleChannel字段上调用appIsResumed。
当 FlutterView 或 Activity 不再可见时,也必须调用 detachFromFlutterEngine 以及 LifecycleChannel 类上的其他生命周期方法,以避免资源泄漏。
此外,请查看 FlutterViewEngine 演示类或 FlutterActivityAndFragmentDelegate 中的其余实现,以确保剪贴板、系统 UI 覆盖层、插件等其他功能正常工作。
内容尺寸视图
#通常,FlutterView 需要通过自身尺寸或匹配父级尺寸来确定固定尺寸。这可以在示例项目中看到。不过,现在也可以让 FlutterView 根据其内容自行调整大小。通过将高度或宽度设置为 content_wrap,FlutterView 可以自行调整大小,如内容尺寸示例项目所示。
- 若要在部署应用时启用内容尺寸视图(Content-sized view),请在项目的
AndroidManifest.xml文件中的<application>标签下添加以下设置:
<meta-data
android:name="io.flutter.embedding.android.EnableContentSizing"
android:value="true" />
限制
#由于内容尺寸的 Flutter 视图要求 Flutter 应用能够自行调整大小,因此不支持某些小部件。
- 具有无限大小的小部件,如
ListView。 - 将其大小推迟给子级的小部件,如
LayoutBuilder。
在实践中,这意味着许多常见的小部件都不受支持,例如 ScaffoldBuilder、CupertinoTimerPicker,或任何内部依赖 LayoutBuilder 的小部件。如有疑问,可以使用 UnconstrainedBox 来测试小部件在内容尺寸视图中的可用性,如下例所示:
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!')]),
)
);
}
}