將 Flutter 檢視新增到 Android 應用
瞭解如何透過 Flutter Views 執行高階整合。
與之前介紹的 FlutterActivity 和 FlutterFragment 相比,透過 FlutterView 進行整合需要更多的工作。
從根本上講,Dart 端的 Flutter 框架需要訪問各種 Activity 級別的事件和生命週期才能正常執行。由於 FlutterView(它是一個 android.view.View)可以新增到開發者應用程式擁有的任何 Activity 中,且 FlutterView 本身無法訪問 Activity 級別的事件,因此開發者必須手動將這些連線橋接到 FlutterEngine。
你選擇如何將應用程式 Activity 的事件傳遞給 FlutterView,取決於你的具體應用需求。
示例
#
與 FlutterActivity 和 FlutterFragment 的指南不同,FlutterView 整合透過示例專案演示效果更好。
示例專案地址:https://github.com/flutter/samples/tree/main/add_to_app/android_view。該專案記錄了一個簡單的 FlutterView 整合,其中 FlutterView 被用作 RecycleView 卡片列表中的部分單元格(如上圖 GIF 所示)。
通用方法
#FlutterView 級別整合的核心要點是:你必須在自己的應用程式程式碼中,重新建立 FlutterActivityAndFragmentDelegate 中存在的 Activity、FlutterView 和 FlutterEngine 之間的各種互動。在使用 FlutterActivity 或 FlutterFragment 時,這些連線會自動完成;但由於在本例中 FlutterView 是被新增到應用程式的 Activity 或 Fragment 中,你必須手動重新建立這些連線。否則,FlutterView 將無法渲染任何內容,或者出現其他功能缺失的情況。
示例 FlutterViewEngine 類展示了一種在 Activity、FlutterView 和 FlutterEngine 之間進行特定於應用程式連線的實現方式。
需要實現的 API
#要使 Flutter 能夠繪製內容,絕對必要的最小實現步驟是:
- 當
FlutterView被新增到已恢復(resumed)的Activity檢視層級中並可見時,呼叫attachToFlutterEngine;以及 - 當託管
FlutterView的Activity可見時,在FlutterEngine的lifecycleChannel欄位上呼叫appIsResumed。
當 FlutterView 或 Activity 不再可見時,也必須呼叫 detachFromFlutterEngine 以及 LifecycleChannel 類上的其他生命週期方法,以避免資源洩漏。
此外,請檢視 FlutterViewEngine 演示類或 FlutterActivityAndFragmentDelegate 中的其餘實現,以確保剪貼簿、系統 UI 覆蓋層、外掛等其他功能正常工作。
內容尺寸檢視
#通常,FlutterView 需要透過自身尺寸或匹配父級尺寸來確定固定尺寸。這可以在示例專案中看到。不過,現在也可以讓 FlutterView 根據其內容自行調整大小。透過將高度或寬度設定為 content_wrap,FlutterView 可以自行調整大小,如內容尺寸示例專案所示。
- 若要在部署應用時啟用內容尺寸檢視(Content-sized view),請在專案的
AndroidManifest.xml檔案中的<application>標籤下新增以下設定:
<meta-data
android:name="io.flutter.embedding.android.EnableContentSizing"
android:value="true" />
限制
#由於內容尺寸的 Flutter 檢視要求 Flutter 應用能夠自行調整大小,因此不支援某些小部件。
- 具有無限大小的小部件,如
ListView。 - 將其大小推遲給子級的小部件,如
LayoutBuilder。
在實踐中,這意味著許多常見的小部件都不受支援,例如 ScaffoldBuilder、CupertinoTimerPicker,或任何內部依賴 LayoutBuilder 的小部件。如有疑問,可以使用 UnconstrainedBox 來測試小部件在內容尺寸檢視中的可用性,如下例所示:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context)
=> MaterialApp(home: MyPage());
}
class MyPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: UnconstrainedBox(
// TODO: Edit this line to check if a widget
// can cause problems with content-sized views.
child: Text('This works!'),
// child: Column(children: [Column(children: [Expanded(child: Text('This blows up!'))])]),
// child: ListView(children: [Text('This blows up!')]),
)
);
}
}