Skip to main content

将Flutter添加到任何Web应用程序

Flutter视图和Web内容可以通过多种方式组合来生成一个Web应用程序。根据你的用例选择以下选项之一:

全页面模式

#

在全页面模式下,Flutter Web应用程序控制整个浏览器窗口,并在渲染时完全覆盖其视口。

这是新的Flutter Web项目的默认嵌入模式,无需额外配置。

html
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <script src="flutter_bootstrap.js" defer></script>
  </body>
</html>

当Flutter Web启动时,如果没有引用multiViewEnabledhostElement,它将使用全页面模式。

要了解有关flutter_bootstrap.js文件的更多信息,请查看自定义应用初始化

iframe嵌入

#

当通过iframe嵌入Flutter Web应用程序时,推荐使用全页面模式。嵌入iframe的页面可以根据需要调整其大小和位置,Flutter将完全填充它。

html
<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_bootstrap.js
js
{{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对象:

js
// 添加视图...
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运行:

multi_view_app.dart
dart
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()小部件的副本:

main.dart
dart
void main() {
  runWidget(
    MultiViewApp(
      viewBuilder: (BuildContext context) => const MyApp(),
    ),
  );
}

识别视图

#

每个FlutterView在附加时都会由Flutter分配一个标识符。此viewId可用于唯一标识每个视图,检索其初始配置或决定在其上渲染什么内容。

渲染的FlutterViewviewId可以像这样从其BuildContext中检索:

dart
class SomeWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 检索构建此Widget的`viewId`:
    final int viewId = View.of(context).viewId;
    // ...

类似地,从MultiViewAppviewBuilder方法中,可以像这样检索viewId

dart
MultiViewApp(
  viewBuilder: (BuildContext context) {
    // 检索构建此Widget的`viewId`:
    final int viewId = View.of(context).viewId;
    // 根据`viewId`决定要渲染的内容...
  },
)

阅读有关View.of构造函数的更多信息。

初始视图配置

#

Flutter视图可以在启动时从JS接收任何初始化数据。这些值通过addView方法的initialData属性传递,如下所示:

js
// 添加具有初始数据的视图...
let viewId = app.addView({
  hostElement: someElement,
  initialData: {
    greeting: 'Hello, world!',
    randomValue: Math.floor(Math.random() * 100),
  }
});

在Dart中,initialData作为JSAny对象可用,可以通过dart:ui_web库中的顶级views属性访问。数据通过当前视图的viewId访问,如下所示:

dart
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;

要了解如何定义YourJsInteropType类来映射从JS传递的initialData对象,以便在你的Dart程序中它是类型安全的,请查看dart.dev上的JS互操作性

视图约束

#

默认情况下,嵌入式Flutter Web视图将hostElement的大小视为一个不可变属性,并将其布局严格约束到可用空间。

在Web上,元素的固有大小通常会影响页面的布局(例如,可以围绕它们重新调整内容的imgp标签)。

向Flutter Web添加视图时,你可以使用约束来配置它,这些约束会告知Flutter视图如何布局:

js
// 添加具有初始数据的视图...
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

js
_flutter.loader.load({
  config: {
    hostElement: document.getElementById('flutter_host'),
  }
});

要了解有关其他配置选项的更多信息,请查看自定义Web应用程序初始化