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 上的Inline Frame element 文件。

嵌入模式

#

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, // 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 小部件下。

以下示例來自“多檢視遊樂場”,它將上述內容封裝在 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,或開發期間使用的多檢視遊樂場儲存庫

在 Dart 中將 runApp 替換為 runWidget

#

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 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 樣式相容。例如,Flutter 不會嘗試“修復”矛盾的常量,例如在 CSS 中傳遞 max-height: 100px,而在 Flutter 中傳遞 maxHeight: Infinity

要了解更多資訊,請檢視ViewConstraints理解約束

自定義元素 (hostElement)

#

在 Flutter 3.10 和 3.24 之間
您可以將單檢視 Flutter Web 應用嵌入到您網頁的任何 HTML 元素中。

要告知 Flutter Web 將哪個元素渲染到其中,請向 _flutter.loader.load 函式傳遞一個包含 config 欄位的物件,該物件將 HTMLElement 指定為 hostElement

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

要了解有關其他配置選項的更多資訊,請參閱自定義 Web 應用初始化