跳至主要內容

將 Flutter 新增至任何網頁應用程式

Flutter 視圖和網頁內容可以透過不同的方式組合以產生網頁應用程式。請根據您的使用情境選擇下列其中一種:

  • Flutter 視圖控制整個頁面 ( 全頁模式 )
  • 將 Flutter 視圖新增至現有的網頁應用程式 ( 嵌入模式 )

全頁模式

#

在全頁模式中,Flutter 網頁應用程式會控制整個瀏覽器視窗,並在渲染時完全覆蓋其視口。

這是新 Flutter 網頁專案的預設嵌入模式,不需要額外的設定。

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

當啟動 Flutter 網頁時,如果沒有參考 multiViewEnabledhostElement,它會使用全頁模式。

若要進一步了解 flutter_bootstrap.js 檔案,請查看自訂應用程式初始化

iframe 嵌入

#

當透過 iframe 嵌入 Flutter 網頁應用程式時,建議使用全頁模式。嵌入 iframe 的頁面可以根據需要調整其大小和位置,而 Flutter 會將其完全填滿。

html
<iframe src="https://url-to-your-flutter/index.html"></iframe>

若要進一步了解 iframe 的優缺點,請查看 MDN 上的內聯框架元素文件。

嵌入模式

#

Flutter 網頁應用程式也可以將內容渲染到另一個網頁應用程式的任意數量元素(通常是 div)中;這稱為「嵌入模式」(或「多視圖」)。

在此模式中:

  • Flutter 網頁應用程式可以啟動,但直到使用 addView 新增第一個「視圖」之前都不會渲染。
  • 主機應用程式可以從嵌入的 Flutter 網頁應用程式中新增或移除視圖。
  • 當新增或移除視圖時,Flutter 應用程式會收到通知,因此它可以相應地調整其 widget。

啟用多檢視模式

#

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 widgetView widget 可以分組在 ViewCollection widget 下。

以下範例來自多視圖遊樂場,將上述內容封裝在可用作應用程式根 widget 的 MultiViewApp widget 中。對於每個 FlutterView,都會執行一個 WidgetBuilder 函式

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 網頁的多視圖模式中,implicitView 不再存在,因此 runApp 會開始失敗並出現 Unexpected null value 錯誤。

在多視圖模式中,您的 main.dart 必須改為呼叫 runWidget 函式。它不需要 implicitView,而且只會渲染到明確新增到應用程式中的視圖。

以下範例使用上述描述的 MultiViewApp,在每個可用的 FlutterView 上渲染 MyApp() widget 的副本:

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 互通性

檢視條件約束

#

預設情況下,嵌入的 Flutter 網頁視圖會將其 hostElement 的大小視為不可變的屬性,並將其版面配置緊密限制在可用空間內。

在網頁上,元素的固有大小通常會影響頁面的版面配置(例如可以將內容環繞在它們周圍的 imgp 標籤)。

當新增視圖到 Flutter 網頁時,您可以設定限制條件,告知 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,但傳遞 maxHeight: Infinity 給 Flutter。

若要進一步了解,請查看 ViewConstraints 類別,以及了解限制條件

自訂元素 (hostElement)

#

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

若要告知 Flutter 網頁要渲染到哪個元素,請將帶有 config 欄位的物件傳遞給 _flutter.loader.load 函式,該欄位會將 HTMLElement 指定為 hostElement

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

若要進一步了解其他設定選項,請查看自訂網頁應用程式初始化