在 Android 應用中新增 Flutter 頁面

本指南講述瞭如何在一個現有的 Android 應用中新增單個 Flutter 頁面。新增到應用中的單個 Flutter 頁面可以是不透明的普通頁面,也可以是透明的頁面。這兩種頁面的使用都會在本指南中提到。

新增一個普通的 Flutter 頁面

Add Flutter Screen Header

步驟 1:在 AndroidManifest.xml 中新增 FlutterActivity

Flutter 提供了 FlutterActivity,用於在 Android 應用內部展示一個 Flutter 的互動介面。和其他的 Activity 一樣,FlutterActivity 必須在專案的 AndroidManifest.xml 檔案中註冊。將下邊的 XML 程式碼新增到你的 AndroidManifest.xml 檔案中的 application 標籤內:

<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 主題。主題的選擇決定 Android 系統展示框架所使用的顏色,例如 Android 的導航欄,以及 Flutter UI 自身的第一次渲染前 FlutterActivity 的背景色。

步驟 2:載入 FlutterActivity

在你的清單檔案中註冊了 FlutterActivity 之後,根據需要,你可以在應用中的任意位置新增開啟 FlutterActivity 的程式碼。下邊的程式碼展示瞭如何在 OnClickListener 的點選事件中開啟 FlutterActivity

myButton.setOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity.createDefaultIntent(currentActivity)
    );
  }
});
myButton.setOnClickListener {
  startActivity(
    FlutterActivity.createDefaultIntent(this)
  )
}

上述的例子假定了你的 Dart 程式碼入口是呼叫 main(),並且你的 Flutter 初始路由是 ‘/’。 Dart 程式碼入口不能透過 Intent 改變,但是初始路由可以透過 Intent 來修改。下面的例子講解了如何開啟一個自訂 Flutter 初始路由的 FlutterActivity

myButton.addOnClickListener(new OnClickListener() {
  @Override
  public void onClick(View v) {
    startActivity(
      FlutterActivity
        .withNewEngine()
        .initialRoute("/my_route")
        .build(currentActivity)
      );
  }
});
myButton.setOnClickListener {
  startActivity(
    FlutterActivity
      .withNewEngine()
      .initialRoute("/my_route")
      .build(this)
  )
}

可以用你想要的初始路由替換掉 "/my_route"

工廠方法 withNewEngine() 可以用於配置一個 FlutterActivity,它會在內部建立一個屬於自己的 FlutterEngine 例項,這會有一個明顯的初始化時間。另外一種可選的做法是讓 FlutterActivity 使用一個預熱且快取的 FlutterEngine,這可以最小化 Flutter 初始化的時間。這種方式接下來會討論到。

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

每一個 FlutterActivity 預設會建立它自己的 FlutterEngine。每一個 FlutterEngine 會有一個明顯的預熱時間。這意味著載入一個標準的 FlutterActivity 時,在你的 Flutter 互動頁面可見之前會有一個短暫的延遲。想要最小化這個延遲時間,你可以在抵達你的 FlutterActivity 之前,初始化一個 FlutterEngine,然後使用這個已經預熱好的 FlutterEngine

要預熱一個 FlutterEngine,可以在你的應用中找一個合理的地方例項化一個 FlutterEngine。下面的這個例子是在 Application 類中預先初始化一個 FlutterEngine

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);
  }
}
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)
  }
}

傳給 FlutterEngineCache 的 ID 可以是你想要的任何名稱。確保 FlutterActivityFlutterFragment 在使用快取的 FlutterEngine 時,傳遞了同樣的 ID。基於快取的 FlutterEngine 來使用 FlutterActivity 會在後續討論到。

要使用預熱且快取的 FlutterEngine 時,讓你的 FlutterActivity 從快取中獲取 FlutterEngine,而不是建立一個新的。可以使用 FlutterActivitywithCachedEngine() 方法來實現:

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

當使用 withCachedEngine() 方法時,傳遞你快取對應 FlutterEngine 時用的相同 ID。

現在,當你載入 FlutterActivity 時,在展示 Flutter 內容前的延遲會明顯降低。

為快取的 FlutterEngine 設定初始路由

當配置一個使用新 FlutterEngineFlutterActivity 或者 FlutterFragment 時,會使用到初始路由的概念。但是,使用快取中的 Flutter 引擎時, FlutterActivity 或者 FlutterFragment 則沒有涉及初始路由的概念。這是因為被快取的引擎理論上已經執行了 Dart 程式碼,在這時配置初始路由已經太遲了。

開發者如果想要讓快取中的引擎從自訂的初始路由開始執行,那麼可以執行 Dart 入口前,為快取的 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);
  }
}
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)
  }
}

透過設定導航通道中的初始路由,會讓關聯的 FlutterEnginerunApp() 方法首次執行後,展示已配置的路由頁面。

runApp() 的首次執行之後,修改導航通道中的初始路由屬性是不會生效的。想要在不同的 ActivityFragment 之間使用同一個 FlutterEngine,並且在其展示時切換不同的路由,開發者需要設定一個方法通道,來明確地通知他們的 Dart 程式碼切換 Navigator 路由。

新增一個透明主題的 FlutterActivity

Add Flutter Screen With Translucency Header

大部分的全屏 Flutter 互動頁面是不透明的。但是,一些應用可能會發佈一個類似模態框的 Flutter 頁面,例如,一個對話方塊或者底部工作表。 Flutter 預設支援透明的 FlutterActivity

要將你的 FlutterActivity 設定為透明,在建立和載入 FlutterActivity 的常規步驟中做如下的變更。

步驟 1:使用透明的主題

Android 需要一個特殊的主題屬性來讓 Activity 以一個透明的背景渲染。使用如下屬性來建立或者修改一個 Android 主題:

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

然後,將透明主題應用到你的 FlutterActivity

<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 已經支援透明化。下一步,你需要在開啟 FlutterActivity 時顯式啟用透明模式。

步驟 2:啟動透明的 FlutterActivity

要開啟透明背景的 FlutterActivity,需要把對應的 BackgroundMode 傳遞給 IntentBuilder

// 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)
);
// 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)
);

現在你的 FlutterAcivity 的背景已經是透明的了。