將 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 及其要求。
將 FlutterFragment 新增到具有新 FlutterEngine 的 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)
}
}</string?>
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,可以避免大部分時間開銷,下面將討論這一點。
使用預熱的 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 並切換這些顯示的路由的開發者,需要設定一個方法通道並明確指示其 Dart 程式碼更改 Navigator 路由。
顯示啟動螢幕
#即使使用預熱的 FlutterEngine,Flutter 內容的初始顯示也需要一些等待時間。為了幫助改善圍繞此短暫等待期的使用者體驗,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 String 名稱中不包含括號 ()。
控制 FlutterFragment 的渲染模式
#FlutterFragment 可以使用 SurfaceView 來渲染其 Flutter 內容,也可以使用 TextureView。預設是 SurfaceView,其效能遠優於 TextureView。但是,SurfaceView 不能嵌入到 Android View 層級的中間。SurfaceView 必須是層級結構中的最底層 View,或者最頂層 View。此外,在 Android N 之前的版本上,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 繪製的任何畫素,該背景為黑色。出於效能原因,使用不透明背景進行渲染是首選渲染模式。在 Android 上使用透明度渲染 Flutter 會對效能產生負面影響。但是,許多設計都需要 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 的關係
#一些應用選擇使用 Fragments 作為整個 Android 螢幕。在這些應用中,Fragment 控制系統 UI(如 Android 的狀態列、導航欄和方向)是合理的。

在其他應用中,Fragments 僅用於表示 UI 的一部分。FlutterFragment 可用於實現抽屜的內部、影片播放器或單個卡片。在這些情況下,FlutterFragment 影響 Android 的系統 UI 是不合適的,因為在同一個 Window 中還有其他 UI 部分。

FlutterFragment 有一個概念,有助於區分 FlutterFragment 應該能夠控制其宿主 Activity 的情況,以及 FlutterFragment 應該隻影響其自身行為的情況。為了防止 FlutterFragment 將其 Activity 暴露給 Flutter 外掛,並防止 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();向 shouldAttachEngineToActivity() Builder 方法傳遞 false 會阻止 Flutter 與周圍的 Activity 進行互動。預設值為 true,允許 Flutter 和 Flutter 外掛與周圍的 Activity 進行互動。