将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, // 启用嵌入模式。
});
let app = await engine.runApp();
// 使这个`app`对象可用于你的JS应用程序。
}
});
从JS管理Flutter视图
#要添加或删除视图,请使用runApp
方法返回的app
对象:
// 添加视图...
let viewId = app.addView({
hostElement: document.querySelector('#some-element'),
});
// 删除viewId...
let viewConfig = app.removeView(viewId);
从Dart处理视图更改
#通过WidgetsBinding
类的didChangeMetrics
方法 向Flutter显示视图的添加和删除。
附加到你的Flutter应用程序的完整视图列表可通过WidgetsBinding.instance.platformDispatcher.views
迭代器获得。这些视图的类型为FlutterView
。
要将内容渲染到每个FlutterView
,你的Flutter应用程序需要创建一个View
小部件。View
小部件可以组合在一个ViewCollection
小部件下。
以下示例来自 多视图游乐场 ,它将上述内容封装在一个MultiViewApp
小部件中,该小部件可以用作应用程序的根小部件。一个WidgetBuilder
函数 为每个FlutterView
运行:
import 'dart:ui' show FlutterView;
import 'package:flutter/widgets.dart';
/// 为添加到应用程序的每个视图调用[viewBuilder]以获取要渲染到该视图中的小部件。可以使用[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);
// 需要为所有视图重新评估viewBuilder回调。
_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
mixin,或开发过程中使用的多视图游乐场repo。
用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
可用于唯一标识每个视图,检索其初始配置或决定在其上渲染什么内容。
渲染的FlutterView
的viewId
可以像这样从其BuildContext
中检索:
class SomeWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 检索构建此Widget的`viewId`:
final int viewId = View.of(context).viewId;
// ...
类似地,从MultiViewApp
的viewBuilder
方法中,可以像这样检索viewId
:
MultiViewApp(
viewBuilder: (BuildContext context) {
// 检索构建此Widget的`viewId`:
final int viewId = View.of(context).viewId;
// 根据`viewId`决定要渲染的内容...
},
)
阅读有关View.of
构造函数的更多信息。
初始视图配置
#Flutter视图可以在启动时从JS接收任何初始化数据。这些值通过addView
方法的initialData
属性传递,如下所示:
// 添加具有初始数据的视图...
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视图如何布局:
// 添加具有初始数据的视图...
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 到 3.24 之间
你可以将单视图Flutter Web应用程序嵌入到网页的任何HTML元素中。
要告诉Flutter Web渲染到哪个元素,请将包含config
字段的对象传递给_flutter.loader.load
函数,该字段指定HTMLElement
作为hostElement
。
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});
要了解有关其他配置选项的更多信息,请查看自定义Web应用程序初始化。
除非另有说明,否则本网站上的文档反映的是 Flutter 的最新稳定版本。页面最后更新于 2025-01-30。 查看源代码 或 报告问题。