Skip to main content

在 Android 應用程式中加入 Flutter 螢幕

學習如何在現有的 Android 應用程式中加入單一 Flutter 螢幕。

本指南說明如何在現有的 Android 應用程式中加入單一 Flutter 螢幕。Flutter 螢幕可以作為一般、不透明的螢幕加入,也可以作為可透視的半透明螢幕加入。本指南將說明這兩種方式。

加入一般 Flutter 螢幕

#
Add Flutter Screen Header

步驟 1:在 AndroidManifest.xml 中加入 FlutterActivity

#

Flutter 提供了 FlutterActivity,可在 Android 應用程式中顯示 Flutter 體驗。和其他 Activity 一樣,FlutterActivity 必須註冊在你的 AndroidManifest.xml 中。請在 AndroidManifest.xml 檔案的 application 標籤下方加入以下 XML:

xml
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/LaunchTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

@style/LaunchTheme 的參考可以替換為任何你想要套用到 FlutterActivity 的 Android 主題(theme)。 主題的選擇會決定應用於 Android 系統 chrome(如 Android 導覽列)的顏色,以及在 Flutter UI 首次渲染前,FlutterActivity 的背景顏色。

步驟 2:啟動 FlutterActivity

#

在你的 manifest 檔案中註冊好 FlutterActivity 之後, 你可以在應用程式中任何你想要的地方,加入程式碼來啟動 FlutterActivity。以下範例展示了 從 OnClickListener 啟動 FlutterActivity 的方式。

ExistingActivity.kt
kotlin
MyButton(onClick = {
    startActivity(
        FlutterActivity.createDefaultIntent(this)
    )
})

@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}
ExistingActivity.kt
kotlin
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}
ExistingActivity.java
java
myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(currentActivity)
    );
  }
});

前述範例假設你的 Dart 進入點(entrypoint)為 main(),且初始的 Flutter 路由為 /。Dart 進入點無法透過 Intent 進行變更,但初始路由可以使用 Intent 來修改。以下範例說明如何啟動一個 FlutterActivity,並在 Flutter 中初始渲染自訂路由。

ExistingActivity.kt
kotlin
MyButton(onClick = {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
})

@Composable
fun MyButton(onClick: () -> Unit) {
    Button(onClick = onClick) {
        Text("Launch Flutter!")
    }
}
ExistingActivity.kt
kotlin
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}
ExistingActivity.java
java
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );
  }
});

"/my_route" 替換為你想要的初始路由。

使用 withNewEngine() 工廠方法會設定一個 FlutterActivity,該物件會在內部自行建立一個 FlutterEngine 實例。這樣會帶來不小的初始化時間。另一種做法是指示 FlutterActivity 使用預先加載並快取的 FlutterEngine,這樣可以將 Flutter 的初始化時間降到最低。接下來將說明這種做法。

步驟 3:(選用)使用快取的 FlutterEngine

#

每個 FlutterActivity 預設都會建立自己的 FlutterEngine。每個 FlutterEngine 都需要一定的預熱時間。這表示啟動標準的 FlutterActivity 時,在你的 Flutter 體驗顯示出來前會有短暫的延遲。為了減少這個延遲,你可以在進入 FlutterActivity 之前先預熱一個 FlutterEngine,然後直接使用這個預熱好的 FlutterEngine

要預熱一個 FlutterEngine,請在你的應用程式中找到合適的位置來實例化 FlutterEngine。以下範例會在 Application 類別中隨機預熱一個 FlutterEngine

MyApplication.kt
kotlin
class MyApplication : Application() {
  lateinit var flutterEngine : FlutterEngine

  override fun onCreate() {
    super.onCreate()

    // Instantiate a FlutterEngine.
    flutterEngine = FlutterEngine(this)

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.dartExecutor.executeDartEntrypoint(
      DartExecutor.DartEntrypoint.createDefault()
    )

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine)
  }
}
MyApplication.java
java
public class MyApplication extends Application {
  public FlutterEngine flutterEngine;

  @Override
  public void onCreate() {
    super.onCreate();
    // Instantiate a FlutterEngine.
    flutterEngine = new FlutterEngine(this);

    // Start executing Dart code to pre-warm the FlutterEngine.
    flutterEngine.getDartExecutor().executeDartEntrypoint(
      DartEntrypoint.createDefault()
    );

    // Cache the FlutterEngine to be used by FlutterActivity.
    FlutterEngineCache
      .getInstance()
      .put("my_engine_id", flutterEngine);
  }
}

傳遞給 FlutterEngineCache 的 ID 可以是任何你想要的值。 請確保你在任何需要使用快取 FlutterEngineFlutterActivityFlutterFragment 中傳遞相同的 ID。 接下來會說明如何將 FlutterActivity 與已快取的 FlutterEngine 搭配使用。

有了預先啟動並快取的 FlutterEngine 之後,你現在需要 指示你的 FlutterActivity 使用這個快取的 FlutterEngine,而不是建立新的實例。 為了達成這個目的,請使用 FlutterActivitywithCachedEngine() builder:

ExistingActivity.kt
kotlin
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withCachedEngine("my_engine_id")
      .build(this)
  )
}
ExistingActivity.java
java
myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withCachedEngine("my_engine_id")
        .build(currentActivity)
      );
  }
});

當你使用 withCachedEngine() 工廠方法時, 請傳入你在快取目標 FlutterEngine 時所使用的相同 ID。

現在,當你啟動 FlutterActivity 時, 顯示 Flutter 內容的延遲會大幅減少。

使用已快取 engine 設定初始路由

#

初始路由的概念可在使用新的 FlutterEngine 設定 FlutterActivityFlutterFragment 時使用。 然而,使用快取引擎時,FlutterActivityFlutterFragment 並不提供初始路由的概念。 這是因為快取引擎預期已在執行 Dart 程式碼,這表示此時已來不及設定初始路由。

希望快取引擎以自訂初始路由啟動的開發者,可以在執行 Dart 進入點之前,設定快取的 FlutterEngine 使用自訂初始路由。以下範例示範如何在快取引擎中使用初始路由:

MyApplication.kt
kotlin
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)
  }
}
MyApplication.java
java
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() 初次執行後再變更導航頻道的初始路由屬性將不會有任何效果。 希望在不同 ActivityFragment 之間共用相同 FlutterEngine 並在這些畫面之間切換路由的開發者,需要設定方法頻道,並明確指示其 Dart 程式碼變更 Navigator 路由。

新增半透明的 Flutter 螢幕

#
Add Flutter Screen With Translucency Header

大多數全螢幕的 Flutter 體驗都是不透明的。 然而,有些應用程式希望部署看起來像模態視窗(modal)的 Flutter 螢幕, 例如對話框(dialog)或底部彈出視窗(bottom sheet)。 Flutter 原生支援半透明的 FlutterActivity

若要讓你的 FlutterActivity 具有半透明效果, 請在建立與啟動 FlutterActivity 的一般流程中進行以下修改。

步驟 1:使用具有半透明屬性的主題

#

Android 要求具有半透明背景的 Activity 必須指定特殊的主題屬性。 請建立或更新一個 Android 主題,並加入以下屬性:

xml
<style name="MyTheme" parent="@style/MyParentTheme">
  <item name="android:windowIsTranslucent">true</item>
</style>

然後,將半透明主題(translucent theme)套用到你的 FlutterActivity

xml
<activity
  android:name="io.flutter.embedding.android.FlutterActivity"
  android:theme="@style/MyTheme"
  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  android:hardwareAccelerated="true"
  android:windowSoftInputMode="adjustResize"
  />

你的 FlutterActivity 現在已支援透明度(translucency)。 接下來,你需要以明確支援透明度的方式啟動你的 FlutterActivity

步驟 2:以透明背景啟動 FlutterActivity

#

若要以透明背景啟動你的 FlutterActivity, 請將適當的 BackgroundMode 傳遞給 IntentBuilder

ExistingActivity.kt
kotlin
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);

// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(this)
);
ExistingActivity.java
java
// Using a new FlutterEngine.
startActivity(
  FlutterActivity
    .withNewEngine()
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);

// Using a cached FlutterEngine.
startActivity(
  FlutterActivity
    .withCachedEngine("my_engine_id")
    .backgroundMode(FlutterActivityLaunchConfigs.BackgroundMode.transparent)
    .build(context)
);

你現在已經擁有一個具有透明背景的 FlutterActivity