跳到主内容

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

了解将 Flutter 视图嵌入 Web 内容的不同方式。

Flutter 视图和 Web 内容可以通过多种方式组合以构建 Web 应用程序。请根据您的用例选择以下方式之一:

  • Flutter 视图控制整个页面(全屏模式
  • 将 Flutter 视图添加到现有的 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 上的 内联框架元素 (Inline Frame element) 文档。

嵌入模式

#

Flutter Web 应用程序还可以将内容渲染到另一个 Web 应用程序的任意数量的元素(通常是 div)中;这被称为“嵌入模式”(或“多视图”)。

在此模式下:

  • Flutter Web 应用程序可以启动,但在使用 addView 添加第一个“视图”之前不会进行渲染。
  • 宿主应用程序可以从嵌入的 Flutter Web 应用程序中添加或删除视图。
  • 当视图被添加或删除时,Flutter 应用程序会收到通知,从而可以相应地调整其组件(widgets)。

启用多视图模式

#

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, // Enables embedded mode.
    });
    let app = await engine.runApp();
    // Make this `app` object available to your JS app.
  }
});

从 JS 管理 Flutter 视图

#

要添加或删除视图,请使用 runApp 方法返回的 app 对象:

js
// 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 运行:

multi_view_app.dart
dart
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 mixin,或开发期间使用的 Multi View Playground 仓库

在 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) {
    // Retrieve the `viewId` where this Widget is being built:
    final int viewId = View.of(context).viewId;
    // ...

同样,从 MultiViewAppviewBuilder 方法中,viewId 可以这样获取:

dart
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 属性传递,如下所示:

js
// 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 进行访问,如下所示:

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

要了解如何定义 YourJsInteropType 类以映射从 JS 传递的 initialData 对象,从而在 Dart 程序中实现类型安全,请查阅 dart.dev 上的 JS 互操作性 (JS Interoperability)

视图约束

#

默认情况下,嵌入的 Flutter Web 视图将其 hostElement 的大小视为不可变属性,并将其布局严格限制在可用空间内。

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

将视图添加到 Flutter Web 时,您可以为其配置约束,以告知 Flutter 该视图应如何进行布局:

js
// Adding a view with initial data...
let viewId = app.addView({
  hostElement: someElement,
  viewConstraints: {
    maxWidth: 320,
    minHeight: 0,
    maxHeight: Infinity,
  }
});

从 JS 传递的视图约束需要与嵌入 Flutter 的 hostElement 的 CSS 样式兼容。例如,如果 CSS 中传递了 max-height: 100px,但给 Flutter 传递了 maxHeight: Infinity,Flutter 不会尝试“修复”这种矛盾的常量。

要了解更多信息,请查看 ViewConstraints理解约束 (Understanding constraints)

自定义元素 (hostElement)

#

您可以将单视图 Flutter Web 应用嵌入到您网页的任何 HTML 元素中。

要告知 Flutter Web 在哪个元素中进行渲染,请将一个带有 config 字段的对象传递给 _flutter.loader.load 函数,该对象指定一个 HTMLElement 作为 hostElement

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

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