將 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 上的Inline Frame element 文件。
嵌入模式
#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, // Enables embedded mode.
});
let app = await engine.runApp();
// Make this `app` object available to your JS app.
}
});從 JS 管理 Flutter 檢視
#要新增或移除檢視,請使用 runApp 方法返回的 app 物件。
// 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 執行。
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() 小部件的副本。
void main() {
runWidget(
MultiViewApp(
viewBuilder: (BuildContext context) => const MyApp(),
),
);
}識別檢視
#每個 FlutterView 在附加時都會由 Flutter 分配一個識別符號。此 viewId 可用於唯一標識每個檢視、檢索其初始配置或決定在其上渲染的內容。
渲染的 FlutterView 的 viewId 可以透過其 BuildContext 檢索,如下所示:
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;
// ...類似地,從 MultiViewApp 的 viewBuilder 方法中,可以這樣檢索 viewId:
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 屬性傳遞,如下所示:
// 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 訪問,如下所示:
final initialData = ui_web.views.getInitialData(viewId) as YourJsInteropType;要了解如何定義 YourJsInteropType 類來對映從 JS 傳遞的 initialData 物件,使其在 Dart 程式中型別安全,請參閱 Dart.dev 上的JS Interoperability。
檢視約束
#預設情況下,嵌入式 Flutter Web 檢視將其 hostElement 的大小視為不可變屬性,並將其佈局嚴格限制在可用空間內。
在 Web 上,元素的固有尺寸會影響頁面佈局(例如,img 或 p 標籤會圍繞它們重排內容)。
新增檢視到 Flutter Web 時,您可能會使用一些約束來配置它,這些約束會告知 Flutter 檢視需要如何佈局。
// 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。
_flutter.loader.load({
config: {
hostElement: document.getElementById('flutter_host'),
}
});要了解有關其他配置選項的更多資訊,請參閱自定義 Web 應用初始化。