將 Flutter Fragment 新增到 Android 應用
瞭解如何將 Flutter Fragment 新增到現有的 Android 應用中。
本指南介紹瞭如何將 Flutter Fragment 新增到現有的 Android 應用中。在 Android 中,Fragment 代表更大 UI 的一個模組化部分。Fragment 可用於呈現側邊抽屜、標籤頁內容、ViewPager 中的頁面,或者僅代表單 Activity 應用中的普通螢幕。Flutter 提供了 FlutterFragment,以便開發者可以在任何可以使用常規 Fragment 的地方呈現 Flutter 體驗。
如果 Activity 同樣適用於您的應用需求,請考慮使用 FlutterActivity 而不是 FlutterFragment,因為前者使用起來更快捷、更簡單。
FlutterFragment 允許開發者控制 Fragment 內 Flutter 體驗的以下細節:
- 初始 Flutter 路由
- 要執行的 Dart 入口點
- 不透明背景與半透明背景
FlutterFragment是否應控制其所在的Activity- 應使用新的
FlutterEngine還是快取的FlutterEngine
FlutterFragment 還附帶了許多必須從其所在 Activity 轉發的呼叫。這些呼叫允許 Flutter 適當地響應作業系統事件。
本指南介紹了各種 FlutterFragment 及其要求。
使用新的 FlutterEngine 將 FlutterFragment 新增到 Activity
#
使用 FlutterFragment 的第一步是將其新增到宿主 Activity 中。
要將 FlutterFragment 新增到宿主 Activity,請在 Activity 的 onCreate() 方法中(或在適合您應用的任何其他時間)例項化並附加一個 FlutterFragment 例項。
class MyActivity : FragmentActivity() {
companion object {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private const val TAG_FLUTTER_FRAGMENT = "flutter_fragment"
}
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private var flutterFragment: FlutterFragment? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Inflate a layout that has a container for your FlutterFragment. For
// this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout)
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
val fragmentManager: FragmentManager = supportFragmentManager
// Attempt to find an existing FlutterFragment, in case this is not the
// first time that onCreate() was run.
flutterFragment = fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT) as FlutterFragment?
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
var newFlutterFragment = FlutterFragment.createDefault()
flutterFragment = newFlutterFragment
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
newFlutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit()
}
}
}
public class MyActivity extends FragmentActivity {
// Define a tag String to represent the FlutterFragment within this
// Activity's FragmentManager. This value can be whatever you'd like.
private static final String TAG_FLUTTER_FRAGMENT = "flutter_fragment";
// Declare a local variable to reference the FlutterFragment so that you
// can forward calls to it later.
private FlutterFragment flutterFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Inflate a layout that has a container for your FlutterFragment.
// For this example, assume that a FrameLayout exists with an ID of
// R.id.fragment_container.
setContentView(R.layout.my_activity_layout);
// Get a reference to the Activity's FragmentManager to add a new
// FlutterFragment, or find an existing one.
FragmentManager fragmentManager = getSupportFragmentManager();
// Attempt to find an existing FlutterFragment,
// in case this is not the first time that onCreate() was run.
flutterFragment = (FlutterFragment) fragmentManager
.findFragmentByTag(TAG_FLUTTER_FRAGMENT);
// Create and attach a FlutterFragment if one does not exist.
if (flutterFragment == null) {
flutterFragment = FlutterFragment.createDefault();
fragmentManager
.beginTransaction()
.add(
R.id.fragment_container,
flutterFragment,
TAG_FLUTTER_FRAGMENT
)
.commit();
}
}
}
上述程式碼足以呈現一個 Flutter UI,該 UI 從呼叫您的 main() Dart 入口點開始,初始 Flutter 路由為 /,並使用一個新的 FlutterEngine。但是,此程式碼不足以實現所有預期的 Flutter 行為。Flutter 依賴於各種必須從您的宿主 Activity 轉發到 FlutterFragment 的作業系統訊號。這些呼叫如下例所示:
class MyActivity : FragmentActivity() {
override fun onPostResume() {
super.onPostResume()
flutterFragment!!.onPostResume()
}
override fun onNewIntent(@NonNull intent: Intent) {
flutterFragment!!.onNewIntent(intent)
}
override fun onBackPressed() {
flutterFragment!!.onBackPressed()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String?>,
grantResults: IntArray
) {
flutterFragment!!.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
)
}
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
flutterFragment!!.onActivityResult(
requestCode,
resultCode,
data
)
}
override fun onUserLeaveHint() {
flutterFragment!!.onUserLeaveHint()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
flutterFragment!!.onTrimMemory(level)
}
}
public class MyActivity extends FragmentActivity {
@Override
public void onPostResume() {
super.onPostResume();
flutterFragment.onPostResume();
}
@Override
protected void onNewIntent(@NonNull Intent intent) {
flutterFragment.onNewIntent(intent);
}
@Override
public void onBackPressed() {
flutterFragment.onBackPressed();
}
@Override
public void onRequestPermissionsResult(
int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults
) {
flutterFragment.onRequestPermissionsResult(
requestCode,
permissions,
grantResults
);
}
@Override
public void onActivityResult(
int requestCode,
int resultCode,
@Nullable Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
flutterFragment.onActivityResult(
requestCode,
resultCode,
data
);
}
@Override
public void onUserLeaveHint() {
flutterFragment.onUserLeaveHint();
}
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
flutterFragment.onTrimMemory(level);
}
}
隨著作業系統訊號轉發到 Flutter,您的 FlutterFragment 將按預期工作。現在,您已將 FlutterFragment 新增到現有的 Android 應用中。
最簡單的整合路徑是使用新的 FlutterEngine,它需要相當長的初始化時間,導致在 Flutter 初始化並首次渲染之前出現空白 UI。使用快取的、預熱的 FlutterEngine 可以避免大部分時間開銷,這一點將在接下來討論。
使用預熱(pre-warmed)的 FlutterEngine
#
預設情況下,FlutterFragment 會建立自己的 FlutterEngine 例項,這需要相當長的預熱時間。這意味著使用者會短暫地看到一個空白的 Fragment。透過使用現有的、預熱的 FlutterEngine 例項,可以減輕大部分預熱時間。
要在 FlutterFragment 中使用預熱的 FlutterEngine,請使用 withCachedEngine() 工廠方法例項化一個 FlutterFragment。
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
val flutterEngine = FlutterEngine(context)
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
)
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
FlutterFragment.withCachedEngine("my_engine_id").build()
// Somewhere in your app, before your FlutterFragment is needed,
// like in the Application class ...
// Instantiate a FlutterEngine.
FlutterEngine flutterEngine = new FlutterEngine(context);
// Start executing Dart code in the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the pre-warmed FlutterEngine to be used later by FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
FlutterFragment.withCachedEngine("my_engine_id").build();
FlutterFragment 在內部知道 FlutterEngineCache,並根據提供給 withCachedEngine() 的 ID 檢索預熱的 FlutterEngine。
透過提供如上所示的預熱 FlutterEngine,您的應用可以儘快渲染出第一幀 Flutter 內容。
使用快取引擎時的初始路由
#當使用新的 FlutterEngine 配置 FlutterActivity 或 FlutterFragment 時,可以使用初始路由的概念。然而,當使用快取引擎時,FlutterActivity 和 FlutterFragment 不提供初始路由的概念。這是因為快取的引擎預期已經運行了 Dart 程式碼,這意味著配置初始路由為時已晚。
希望快取引擎從自定義初始路由開始的開發者,可以在執行 Dart 入口點之前,將快取的 FlutterEngine 配置為使用自定義初始路由。以下示例演示瞭如何在快取引擎中使用初始路由:
class MyApplication : Application() {
lateinit var flutterEngine : FlutterEngine
override fun onCreate() {
super.onCreate()
// Instantiate a FlutterEngine.
flutterEngine = FlutterEngine(this)
// Configure an initial route.
flutterEngine.navigationChannel.setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.dartExecutor.executeDartEntrypoint(
DartExecutor.DartEntrypoint.createDefault()
)
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine)
}
}
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Instantiate a FlutterEngine.
flutterEngine = new FlutterEngine(this);
// Configure an initial route.
flutterEngine.getNavigationChannel().setInitialRoute("your/route/here");
// Start executing Dart code to pre-warm the FlutterEngine.
flutterEngine.getDartExecutor().executeDartEntrypoint(
DartEntrypoint.createDefault()
);
// Cache the FlutterEngine to be used by FlutterActivity or FlutterFragment.
FlutterEngineCache
.getInstance()
.put("my_engine_id", flutterEngine);
}
}
透過設定導航通道的初始路由,關聯的 FlutterEngine 將在首次執行 runApp() Dart 函式時顯示所需的路由。
在首次執行 runApp() 後更改導航通道的初始路由屬性不會產生任何效果。希望在不同 Activity 和 Fragment 之間使用同一個 FlutterEngine 並在這些顯示之間切換路由的開發者,需要設定一個方法通道(Method Channel),並顯式指示其 Dart 程式碼更改 Navigator 路由。
顯示閃屏(Splash Screen)
#即使使用了預熱的 FlutterEngine,首次顯示 Flutter 內容也需要一些等待時間。為了幫助改善此短暫等待期間的使用者體驗,Flutter 支援在渲染第一幀之前顯示閃屏(也稱為“啟動螢幕”)。有關如何顯示啟動螢幕的說明,請參閱閃屏指南。
以指定的初始路由執行 Flutter
#一個 Android 應用可能包含許多獨立的 Flutter 體驗,在不同的 FlutterFragment 中執行,並使用不同的 FlutterEngine。在這種情況下,每個 Flutter 體驗通常以不同的初始路由(非 / 的路由)開始。為了方便起見,FlutterFragment 的 Builder 允許您指定所需的初始路由,如下所示:
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.initialRoute("myInitialRoute/")
.build();
從指定的入口點執行 Flutter
#與不同的初始路由類似,不同的 FlutterFragment 可能希望執行不同的 Dart 入口點。在典型的 Flutter 應用中,只有一個 Dart 入口點:main(),但您可以定義其他入口點。
FlutterFragment 支援為給定的 Flutter 體驗指定要執行的所需 Dart 入口點。要指定入口點,請構建 FlutterFragment,如下所示:
val flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build()
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.dartEntrypoint("mySpecialEntrypoint")
.build();
此 FlutterFragment 配置將執行名為 mySpecialEntrypoint() 的 Dart 入口點。請注意,dartEntrypoint 字串名稱中不包含括號 ()。
控制 FlutterFragment 的渲染模式
#
FlutterFragment 可以使用 SurfaceView 來渲染其 Flutter 內容,也可以使用 TextureView。預設是 SurfaceView,其效能明顯優於 TextureView。但是,SurfaceView 不能穿插在 Android View 層級結構中。SurfaceView 必須是層級結構中最底層的 View 或最頂層的 View。此外,在 Android N 之前的 Android 版本中,SurfaceView 無法進行動畫處理,因為它們的佈局和渲染與其餘 View 層級結構不同步。如果這些用例中的任何一個是您的應用需求,則需要使用 TextureView 代替 SurfaceView。透過使用 texture RenderMode 構建 FlutterFragment 來選擇 TextureView:
// With a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build()
// With a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.renderMode(FlutterView.RenderMode.texture)
.build();
// With a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.renderMode(FlutterView.RenderMode.texture)
.build();
使用所示配置,生成的 FlutterFragment 將其 UI 渲染到 TextureView。
顯示透明的 FlutterFragment
#
預設情況下,FlutterFragment 使用 SurfaceView 渲染不透明背景。(參見“控制 FlutterFragment 的渲染模式”。)對於任何未由 Flutter 繪製的畫素,該背景為黑色。出於效能考慮,使用不透明背景是首選的渲染模式。Flutter 在 Android 上以透明度進行渲染會對效能產生負面影響。然而,許多設計需要在 Flutter 體驗中使用透明畫素,以便透出底層的 Android UI。因此,Flutter 支援 FlutterFragment 中的半透明度。
要為 FlutterFragment 啟用透明度,請使用以下配置構建它:
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.transparencyMode(FlutterView.TransparencyMode.transparent)
.build();
FlutterFragment 與其 Activity 之間的關係
#
有些應用選擇將 Fragment 用作整個 Android 螢幕。在這些應用中,讓 Fragment 控制系統 UI(如 Android 的狀態列、導航欄和螢幕方向)是合理的。
在其他應用中,Fragment 僅用於表示 UI 的一部分。FlutterFragment 可能被用來實現抽屜、影片播放器或單個卡片的內部內容。在這些情況下,讓 FlutterFragment 影響 Android 系統 UI 是不合適的,因為同一 Window 中還有其他 UI 元件。
FlutterFragment 包含一個概念,有助於區分 FlutterFragment 應該控制其宿主 Activity 的情況,以及 FlutterFragment 僅應影響自身行為的情況。為了防止 FlutterFragment 向 Flutter 外掛暴露其 Activity,並防止 Flutter 控制 Activity 的系統 UI,請在 FlutterFragment 的 Builder 中使用 shouldAttachEngineToActivity() 方法,如下所示:
// Using a new FlutterEngine.
val flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build()
// Using a cached FlutterEngine.
val flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build()
// Using a new FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
.shouldAttachEngineToActivity(false)
.build();
// Using a cached FlutterEngine.
FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id")
.shouldAttachEngineToActivity(false)
.build();
將 false 傳遞給 shouldAttachEngineToActivity() 構建器方法會阻止 Flutter 與周圍的 Activity 互動。預設值為 true,這允許 Flutter 和 Flutter 外掛與周圍的 Activity 互動。