将 Flutter 添加到任何 Web 应用程序
了解将 Flutter 视图嵌入到 Web 内容中的不同方法。
Flutter 视图和 Web 内容可以组合以生成 Web 应用程序,具体方式取决于您的用例。选择以下其中一种:
全屏模式
#在全屏模式下,Flutter Web 应用程序控制整个浏览器窗口,并在渲染时完全覆盖其视口。
这是新 Flutter Web 项目的默认嵌入模式,无需额外配置。
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<script src="flutter_bootstrap.js" defer></script>
</body>
</html>
当 Flutter Web 在不引用 multiViewEnabled 或 hostElement 的情况下启动时,它将使用全屏模式。
要了解有关 flutter_bootstrap.js 文件的更多信息,请查看 自定义应用程序初始化。
iframe 嵌入
#
建议在通过 iframe 嵌入 Flutter Web 应用程序时使用全屏模式。嵌入 iframe 的页面可以根据需要调整其大小和位置,而 Flutter 将完全填充它。
<iframe src="https://url-to-your-flutter/index.html"></iframe>
要了解有关 iframe 的优缺点,请查看 MDN 上的 内联框架元素 文档。
嵌入模式
#Flutter Web 应用程序还可以将内容渲染到另一个 Web 应用程序的任意数量的元素(通常是 div)中;这称为“嵌入模式”(或“多视图”)。
在此模式下
- Flutter Web 应用程序可以启动,但直到添加第一个“视图”才开始渲染,具体使用
addView。 - 宿主应用程序可以添加或删除嵌入的 Flutter Web 应用程序中的视图。
- 当添加或删除视图时,Flutter 应用程序会收到通知,以便它可以相应地调整其小部件。
启用多视图模式
#启用多视图模式,在 initializeEngine 方法中设置 multiViewEnabled: true,如下所示
{{flutter_js}}
{{flutter_build_config}}
_flutter.loader.load({
onEntrypointLoaded: async function onEntrypointLoaded(engineInitializer) {
let engine = await engineInitializer.initializeEngine({
multiViewEnabled: true, // Enables embedded mode.
});
let app = await engine.runApp();
// Make this `app` object available to your JS app.
}
});
从 JS 管理 Flutter 视图
#要添加或删除视图,请使用 runApp 方法返回的 app 对象
// Adding a view...
let viewId = app.addView({
hostElement: document.querySelector('#some-element'),
});
// Removing viewId...
let viewConfig = app.removeView(viewId);
处理来自 Dart 的视图更改
#视图的添加和删除通过 WidgetsBinding 类的 didChangeMetrics 方法 传递到 Flutter。
附加到您的 Flutter 应用程序的完整视图列表可通过 WidgetsBinding.instance.platformDispatcher.views 可迭代获取。这些视图的类型为 FlutterView。
为了在每个 FlutterView 中渲染内容,您的 Flutter 应用程序需要创建一个 View 小部件。View 小部件可以组合在一个 ViewCollection 小部件 下。
以下示例,来自 *Multi View Playground*,将上述内容封装在一个 MultiViewApp 小部件中,可以用作应用程序的根小部件。一个 WidgetBuilder 函数 为每个 FlutterView 运行
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
/// Calls [viewBuilder] for every view added to the app to obtain the widget to
/// render into that view. The current view can be looked up with [View.of].
class MultiViewApp extends StatefulWidget {
const MultiViewApp({super.key, required this.viewBuilder});
final WidgetBuilder viewBuilder;
@override
State<MultiViewApp> createState() => _MultiViewAppState();
}
class _MultiViewAppState extends State<MultiViewApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_updateViews();
}
@override
void didUpdateWidget(MultiViewApp oldWidget) {
super.didUpdateWidget(oldWidget);
// Need to re-evaluate the viewBuilder callback for all views.
_views.clear();
_updateViews();
}
@override
void didChangeMetrics() {
_updateViews();
}
Map<Object, Widget> _views = <Object, Widget>{};
void _updateViews() {
final Map<Object, Widget> newViews = <Object, Widget>{};
for (final FlutterView view in WidgetsBinding.instance.platformDispatcher.views) {
final Widget viewWidget = _views[view.viewId] ?? _createViewWidget(view);
newViews[view.viewId] = viewWidget;
}
setState(() {
_views = newViews;
});
}
Widget _createViewWidget(FlutterView view) {
return View(
view: view,
child: Builder(
builder: widget.viewBuilder,
),
);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return ViewCollection(views: _views.values.toList(growable: false));
}
}
有关更多信息,请查看 API 文档中的 WidgetsBinding 混合,或在开发期间使用的 Multi View Playground 仓库。
在 Dart 中用 runWidget 替换 runApp
#Flutter 的 runApp 函数 假定至少有一个可用于渲染的视图(implicitView),但在 Flutter Web 的多视图模式下,implicitView 不再存在,因此 runApp 将开始出现 Unexpected null value 错误。
在多视图模式下,您的 main.dart 必须调用 runWidget 函数。它不需要 implicitView,并且只会渲染到显式添加到您的应用程序中的视图。
以下示例使用上述 MultiViewApp 在每个可用的 FlutterView 上渲染 MyApp() 小部件的副本
void main() {
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyApp(),
),
);
}
识别视图
#每个 FlutterView 在附加时由 Flutter 分配一个标识符。可以使用此 viewId 唯一标识每个视图、检索其初始配置或决定在其中渲染什么。
可以这样从其 BuildContext 中检索渲染的 FlutterView 的 viewId
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Retrieve the `viewId` where this Widget is being built:
final int viewId = View.of(context).viewId;
// ...
类似地,从 MultiViewApp 的 viewBuilder 方法中,可以这样检索 viewId
MultiViewApp(
viewBuilder: (BuildContext context) {
// Retrieve the `viewId` where this Widget is being built:
final int viewId = View.of(context).viewId;
// Decide what to render based on `viewId`...
},
)
详细了解 View.of 构造函数。
初始视图配置
#Flutter 视图可以在启动时从 JS 接收任何初始化数据。这些值通过 addView 方法的 initialData 属性传递,如下所示
// Adding a view with initial data...
let viewId = app.addView({
hostElement: someElement,
initialData: {
greeting: 'Hello, world!',
randomValue: Math.floor(Math.random() * 100),
}
});
在 Dart 中,initialData 可用作 JSAny 对象,可通过 dart:ui_web 库中的顶级 views 属性访问。通过当前视图的 viewId 访问数据,如下所示
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;
要了解如何定义 YourJsInteropType 类以映射从 JS 传递的 initialData 对象,使其在您的 Dart 程序中类型安全,请查看 dart.dev 上的 JS 互操作性。
视图约束
#默认情况下,嵌入的 Flutter Web 视图将 hostElement 的大小视为不变属性,并将其布局严格限制在可用空间内。
在 Web 上,元素的固有大小通常会影响页面的布局(例如,可以围绕它们重新排版内容的 img 或 p 标签)。
将视图添加到 Flutter Web 时,您可以配置它,以告知 Flutter 需要如何布局该视图
// Adding a view with initial data...
let viewId = app.addView({
hostElement: someElement,
viewConstraints: {
maxWidth: 320,
minHeight: 0,
maxHeight: Infinity,
}
});
从 JS 传递的视图约束需要与嵌入 Flutter 的 hostElement 的 CSS 样式兼容。例如,Flutter 不会尝试“修复”矛盾的常量,例如在 CSS 中传递 max-height: 100px,但在 Flutter 中传递 maxHeight: Infinity。
要了解更多信息,请查看 ViewConstraints 类 和 了解约束。
自定义元素 (hostElement)
#
从 Flutter 3.10 版本开始,您可以将单视图 Flutter Web 应用程序嵌入到 Web 页面的任何 HTML 元素中。
要告诉 Flutter Web 要渲染到哪个元素,请将包含指定 HTMLElement 作为 hostElement 的 config 字段的对象传递给 _flutter.loader.load 函数。
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});
要了解有关其他配置选项的更多信息,请查看 自定义 Web 应用程序初始化。