アクティビティの埋め込み

アクティビティの埋め込みは、大画面デバイス上のアプリのタスク ウィンドウを 2 つのアクティビティ、または同じアクティビティの 2 つのインスタンスに分割することでアプリを最適化する機能です。

図 1. 設定アプリのアクティビティを並べて表示

アプリが複数のアクティビティで構成されている場合、アクティビティの埋め込みを使用すると、次のことができます。 タブレット、折りたたみ式デバイス、ChromeOS デバイスでのユーザー エクスペリエンスが向上します。

アクティビティの埋め込みでは、コードのリファクタリングは必要ありません。デベロッパーは、 XML を作成して、アクティビティを並べて表示する、または積み重ねて表示する 構成ファイルに追加するか、Jetpack WindowManager API 呼び出しを実行します。

小画面のサポートは自動的に維持されます。アプリが小画面のデバイスにある場合、アクティビティは重ねられます。大画面の場合、アクティビティは並べて表示されます。システムは 作成した構成に基づいて表示でき、分岐ロジックなし 必要ありません。

アクティビティの埋め込みはデバイスの向きの変更に対応します。折りたたみ式デバイスでもシームレスに動作し、デバイスの折りたたみと展開に合わせて、アクティビティの積み重ねと積み重ね解除を行います。

アクティビティの埋め込みは、Android 12L(API レベル 32)以上を搭載するほとんどの大画面デバイスでサポートされています。

分割タスク ウィンドウ

アクティビティの埋め込みは、アプリのタスク ウィンドウをプライマリとセカンダリという 2 つのコンテナに分割します。コンテナは、メイン アクティビティから起動されたアクティビティ、またはコンテナ内にすでに存在する他のアクティビティから起動されたアクティビティを保持します。

アクティビティは、起動時にセカンダリ コンテナに重ねられ、 セカンダリ コンテナがプライマリ コンテナの上に重ねられているため、 アクティビティの積み重ねと「戻る」ナビゲーションが、 アクティビティに使用できます。

アクティビティの埋め込みを使用すると、さまざまな方法でアクティビティを表示できます。お客様の 2 つのアクティビティを並べて起動することでタスク ウィンドウを分割できる 併用できます。

図 2. 2 つのアクティビティを並べて表示。

また、タスク ウィンドウ全体を占有するアクティビティは、新しいアクティビティを並べて起動することで、タスク ウィンドウを分割できます。

図 3. アクティビティ A がアクティビティ B を横に起動。

すでに分割されてタスク ウィンドウを共有しているアクティビティは、次の方法で他のアクティビティを起動できます。

  • 横のアクティビティの上に重ねて起動:

    図 4. アクティビティ A が、横のアクティビティ B の上にアクティビティ C を起動。
  • 横に、分割を横にシフトして以前のプライマリを隠す アクティビティ:

    図 5. アクティビティ B がアクティビティ C を横に起動し、 分割します
  • アクティビティをその場所の一番上(つまり同じアクティビティ スタック)に起動:

    図 6. アクティビティ B が、追加のインテント フラグなしでアクティビティ C を起動。
  • アクティビティをそのタスクのフルウィンドウで起動:

    図 7. アクティビティ A またはアクティビティ B がアクティビティ C を起動し、アクティビティ C が クリックします。

「戻る」ナビゲーション

アクティビティ間の依存関係やユーザーが「戻る」イベントをトリガーする方法に応じて、分割タスク ウィンドウ状態での「戻る」ナビゲーションのルールはアプリの種類によって異なる場合があります。次に例を示します。

  • 一緒に: アクティビティ同士に関連性があり、一方のアクティビティを他方のアクティビティなしで表示すべきでない場合、両方を終了させるように「戻る」ナビゲーションを設定できます。
  • 単独で: タスク ウィンドウ内のアクティビティ同士が完全に独立している場合、一方のアクティビティの「戻る」ナビゲーションは、他方のアクティビティの状態に影響を与えません。

ボタン ナビゲーションを使用する場合、「戻る」イベントは最後にフォーカスされたアクティビティに送信されます。

ジェスチャー ベースのナビゲーションの場合:

  • Android 14(API レベル 34)以前 - 「戻る」イベントは、ジェスチャーが行われたアクティビティに送信されます。ユーザーが画面の左側からスワイプすると、分割ウィンドウの左側のパネルにあるアクティビティに「戻る」イベントが送信されます。ユーザーが画面の右端から 「戻る」イベントが右側のペインのアクティビティに送信されます。

  • Android 15(API レベル 35)以降

    • 同じアプリの複数のアクティビティを処理する場合、スワイプの方向に関係なく、上位のアクティビティが終了するため、より統一されたエクスペリエンスを提供できます。

    • 異なるアプリの 2 つのアクティビティ(オーバーレイ)が関与するシナリオでは、ボタン ナビゲーションの動作に合わせて、最後にフォーカスされたアクティビティに「戻る」イベントが送信されます。

マルチペイン レイアウト

Jetpack WindowManager を使用すると、Android 12L(API レベル 32)以上を搭載した大画面デバイスと、以前のバージョンのプラットフォームを搭載した一部のデバイスで、マルチペイン レイアウトを埋め込むアクティビティを構築できます。をベースとする既存のアプリ 複数のアクティビティに重点を置いて、 SlidingPaneLayout を使用すると、大画面でのユーザー エクスペリエンスが向上します コードをリファクタリングする必要はありません

一般的な例としては、リストと詳細の分割が挙げられます。質の高い表示を確保するために、システムはリスト アクティビティを起動し、その後アプリが直ちに詳細アクティビティを起動します。遷移システムは、両方のインスタンスが アクティビティが描画されて、まとめて表示されます。ユーザーにとっては、2 つのアクティビティが 1 つのものとして起動します。

図 8. マルチペイン レイアウトで同時に起動した 2 つのアクティビティ。

分割属性

分割されたコンテナ間でのタスク ウィンドウの比率を指定できます コンテナ同士の相対位置関係などです

XML 構成ファイルで定義されているルールについては、次の属性を設定します。

  • splitRatio: コンテナの比率を設定します。値は、開区間(0.0、1.0)の浮動小数点数です。
  • splitLayoutDirection: 分割されたコンテナのレイアウト方法を指定します。 比較できます。次の値があります。
    • ltr: 左から右
    • rtl: 右から左
    • locale: ltr または rtl のどちらかを、ロケール設定から決定します

例については、XML 構成のセクションをご覧ください。

WindowManager API を使用して作成したルールの場合は、SplitAttributes を作成します。 オブジェクトを SplitAttributes.Builder で作成し、次のビルダーを呼び出します。 メソッド:

例については、WindowManager API セクションをご覧ください。

図 9. 2 つのアクティビティの分割は左から右にレイアウトされていますが、分割比率が異なります。

プレースホルダ

プレースホルダ アクティビティは、地図の領域を占有する空のセカンダリ アクティビティです。 アクティビティ分割です。最終的には、別の活動に置き換えられることが意図されています。 表示されます。たとえば、プレースホルダ アクティビティは、 アクティビティ分割のセカンダリ側をリスト / 詳細レイアウトで表示し、 リストが選択されると、詳細を含むアクティビティが 選択したリストアイテムの情報がプレースホルダに置き換わります。

デフォルトでは、プレースホルダは、アクティビティ分割に対応する十分なスペースがある場合にのみ表示されます。プレースホルダは、ディスプレイ サイズが分割を表示できないほど小さな幅または高さに変更されると自動的に終了します。スペースが許せば、再初期化した状態でプレースホルダは再起動されます。

図 10. 折りたたみ式デバイスの折りたたみと展開。プレースホルダ アクティビティは終了し、ディスプレイ サイズが変更されると再作成されます。

ただし、SplitPlaceholderRule またはstickyPlaceholder SplitPlaceholder.BuildersetSticky() メソッドは、 動作しません。属性またはメソッドで true の値が指定されている場合、 システムにより、プレースホルダがタスク ウィンドウの一番上のアクティビティとして表示される ディスプレイのサイズが 2 ペインからシングルペインに縮小される (例については、分割の構成をご覧ください)。

図 11. 折りたたみ式デバイスの折りたたみと展開。プレースホルダのアクティビティは固定されます。

ウィンドウ サイズの変更

デバイス構成の変更時にタスク ウィンドウの幅を狭くして、 マルチペイン レイアウトに十分な大きさ(大画面の折りたたみ式デバイスなど) デバイスがタブレット サイズからスマートフォン サイズに折りたたまれたとき、またはアプリ ウィンドウのサイズが マルチウィンドウ モード)のセカンダリ ペインに表示される、プレースホルダ以外のアクティビティが タスク ウィンドウは、プライマリ ペインのアクティビティの上に重ねて表示されます。

プレースホルダのアクティビティは、分割するために十分な表示幅がある場合にのみ表示されます。小画面では、プレースホルダは自動的に閉じられます。リリースを 表示領域が再び十分な大きさになると、プレースホルダが再作成されます。( プレースホルダ セクションを参照)。

アクティビティを重ねることができるのは、WindowManager がセカンダリ ペインのアクティビティをプライマリ ペインのアクティビティの上に Z オーダーで表示するためです。

セカンダリ ペインの複数のアクティビティ

アクティビティ B が追加のインテント フラグなしで、アクティビティ C を所定の位置で起動します。

アクティビティ C がアクティビティ B の上に重ねられた、アクティビティ A、B、C を含むアクティビティ分割。

その結果、同じタスクのアクティビティの Z オーダーは次のようになります。

アクティビティ B の上に重ねられたアクティビティ C を含むセカンダリ アクティビティ スタック。セカンダリ スタックが、アクティビティ A を含むプライマリ アクティビティ スタックの上に重ねられています。

タスク ウィンドウが小さくなるとアプリは 1 つのアクティビティに縮小され、アクティビティ C がスタックの一番上に重ねられます。

アクティビティ C のみを表示する小さいウィンドウ。

小さいウィンドウで「戻る」操作を行うと、重ねられた各アクティビティ間で移動します。

タスク ウィンドウ構成を、複数のペインに対応できる大きなサイズに復元すると、アクティビティは再び並んで表示されます。

分割の積み重ね

アクティビティ B が、横にアクティビティ C を起動して分割を横にシフトします。

アクティビティ A と B が表示され、次にアクティビティ B と C が表示されるタスク ウィンドウ。

その結果、同じタスクのアクティビティの Z オーダーは次のようになります。

1 つのスタック内にあるアクティビティ A、B、C。アクティビティは上から C、B、A の順に重ねられています。

アプリは、タスク ウィンドウが小さくなると、アクティビティ C が一番上にある 1 つのアクティビティに縮小されます。

アクティビティ C のみを表示する小さいウィンドウ。

固定縦向き

android:screenOrientation マニフェスト設定を使用すると、アプリのアクティビティを縦向きまたは横向きに制限できます。ユーザー エクスペリエンスを向上させるため タブレットや折りたたみ式デバイスなどの大画面デバイス、デバイス メーカー (OEM)は画面の向きのリクエストを無視し、アプリを縦向きにレターボックス表示できます。 横向きディスプレイでは横向き、縦向きディスプレイでは横向き。

図 12. レターボックス表示のアクティビティ: 横向きデバイスでの固定縦向き(左)と、縦向きデバイスでの固定横向き(右)。

同様に、アクティビティの埋め込みが有効になっていると、OEM は大画面デバイス(幅 600 dp 以上)をカスタマイズして、横向き時に固定縦向きのアクティビティをレターボックス表示できます。固定縦向きアクティビティが 2 つ目のアクティビティを起動する場合、2 つのアクティビティを 2 つのペインで並べて表示できます。

図 13. 固定縦向きアクティビティ A が、その横にアクティビティ B を起動。

常に android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED を追加する プロパティをアプリ マニフェスト ファイルに追加して、アプリがサポートしていることをデバイスに通知します。 アクティビティの埋め込み(分割の設定をご覧ください) セクションを参照)。これにより、OEM によるカスタマイズがあるデバイスが、固定縦向きのアクティビティをレターボックス表示するかどうかを判断できます。

分割の設定

アクティビティ分割は、分割ルールによって設定します。XML で分割ルールを定義する または Jetpack WindowManager API を できます。

いずれの場合も、アプリから WindowManager ライブラリにアクセスして、 アプリがアクティビティの埋め込みを実装しているシステム。

次のように対応します。

  1. 次の例のように、アプリのモジュール レベルの build.gradle ファイルに最新の WindowManager ライブラリ依存関係を追加します。

    implementation 'androidx.window:window:1.1.0-beta02'

    WindowManager ライブラリは、アクティビティに必要なすべてのコンポーネントを提供します。 説明します。

  2. アプリがアクティビティの埋め込みを実装していることを、システムに通知します。

    android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED プロパティを追加する <application> まで、要素を追加して検証し、 値を true に設定します。次に例を示します。

    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
        <application>
            <property
                android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
                android:value="true" />
        </application>
    </manifest>
    

    WindowManager リリース 1.1.0-alpha06 以降では、このプロパティをマニフェストに追加して true に設定していないと、アクティビティの埋め込みによる分割が無効になります。

    また、デバイス メーカーはこの設定を使用して、 アクティビティの埋め込みをサポートするアプリです。たとえば、横向きディスプレイに縦向きのみのアクティビティをレターボックス表示し、2 つ目のアクティビティが開始されたら 2 ペイン レイアウトに移行してアクティビティの向きを調整できます(縦向きに固定するをご覧ください)。

XML 構成

アクティビティの埋め込みの実装を XML ベースで作成する手順は次のとおりです。

  1. 次の処理を行う XML リソース ファイルを作成します。

    • 分割を共有するアクティビティを定義する
    • 分割オプションを設定する
    • コンテンツを利用できない場合に分割のセカンダリ コンテナのプレースホルダを作成する
    • 分割に入れないアクティビティを指定する

    次に例を示します。

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. イニシャライザを作成します。

    WindowManager の RuleController コンポーネントは、XML 構成ファイルを解析してシステムで利用できるルールをつくります。Jetpack Startup ライブラリ Initializer は、XML ファイルを使用できるようにします。 RuleController(アプリ起動時にルール)を有効にします。 アクティビティが開始されます。

    イニシャライザを作成する手順は次のとおりです。

    1. Jetpack Startup ライブラリの最新の依存関係をモジュール レベルに追加する build.gradle ファイル。例:

      implementation 'androidx.startup:startup-runtime:1.1.1'

    2. Initializer インターフェースを実装するクラスを作成します。

      イニシャライザは、XML 構成ファイル(main_split_config.xml)の ID を RuleController.parseRules() メソッドに渡すことで、分割ルールを RuleController で使用できるようにします。

      Kotlin

      class SplitInitializer : Initializer<RuleController> {
      
          override fun create(context: Context): RuleController {
              return RuleController.getInstance(context).apply {
                  setRules(RuleController.parseRules(context, R.xml.main_split_config))
              }
          }
      
          override fun dependencies(): List<Class<out Initializer<*>>> {
              return emptyList()
          }
      }

      Java

      public class SplitInitializer implements Initializer<RuleController> {
      
           @NonNull
           @Override
           public RuleController create(@NonNull Context context) {
               RuleController ruleController = RuleController.getInstance(context);
               ruleController.setRules(
                   RuleController.parseRules(context, R.xml.main_split_config)
               );
               return ruleController;
           }
      
           @NonNull
           @Override
           public List<Class<? extends Initializer<?>>> dependencies() {
               return Collections.emptyList();
           }
      }
  3. ルール定義のコンテンツ プロバイダを作成します。

    androidx.startup.InitializationProvider<provider> としてアプリ マニフェスト ファイルに追加します。RuleController イニシャライザ、SplitInitializer の実装への参照を含めます。

    <!-- AndroidManifest.xml -->
    
    <provider android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
        <!-- Make SplitInitializer discoverable by InitializationProvider. -->
        <meta-data android:name="${applicationId}.SplitInitializer"
            android:value="androidx.startup" />
    </provider>
    

    InitializationProviderSplitInitializer を見つけて初期化してから、アプリの onCreate() メソッドを呼び出します。そのため、分割ルールはアプリのメイン アクティビティが起動すると有効になります。

WindowManager API

アクティビティの埋め込みは、少数の API を使用してプログラムで実装できます。 できます。サブクラスの onCreate() メソッドで呼び出しを行います。 Application: アクティビティの前にルールが有効になります。 説明します。

アクティビティの分割をプログラムで作成する手順は、次のとおりです。

  1. 分割ルールを作成します。

    1. SplitPairFilter を作成する 分割を共有するアクティビティを識別します。

      Kotlin

      val splitPairFilter = SplitPairFilter(
         ComponentName(this, ListActivity::class.java),
         ComponentName(this, DetailActivity::class.java),
         null
      )

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
    2. フィルタをフィルタセットに追加します。

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

      Set<SplitPairFilter> filterSet = new HashSet<>();
      filterSet.add(splitPairFilter);
    3. 分割のレイアウト属性を作成します。

      Kotlin

      val splitAttributes: SplitAttributes = SplitAttributes.Builder()
          .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
          .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
          .build()

      Java

      final SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();

      SplitAttributes.Builder は、レイアウトを含むオブジェクトを作成します。 属性:

      • setSplitType(): 使用可能な表示領域を定義します。 各アクティビティ コンテナに割り当てられます。比率分割タイプは、プライマリ コンテナに割り当てられた使用可能な表示領域の比率を指定し、セカンダリ コンテナは残りの使用可能な表示領域を占有します。
      • setLayoutDirection(): プライマリ コンテナから順に、アクティビティ コンテナ同士の相対的なレイアウトを指定します。
    4. SplitPairRule をビルドします。

      Kotlin

      val splitPairRule = SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build()

      Java

      SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
          .setDefaultSplitAttributes(splitAttributes)
          .setMinWidthDp(840)
          .setMinSmallestWidthDp(600)
          .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
          .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
          .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
          .setClearTop(false)
          .build();

      SplitPairRule.Builder がルールを作成して設定します。

      • filterSet: 分割を共有するアクティビティを識別することで、ルールをいつ適用するか判断する分割ペアのフィルタが含まれます。
      • setDefaultSplitAttributes(): レイアウト属性をルールに適用します。
      • setMinWidthDp(): 最小表示幅( 密度非依存ピクセル(dp))。
      • setMinSmallestWidthDp(): デバイスの向きにかかわらず分割を有効にするために、2 つのディスプレイ サイズのうち小さい方が必要とする最小値(dp)を設定します。
      • setMaxAspectRatioInPortrait(): アクティビティの分割が表示される縦向きの最大表示アスペクト比(高さ:幅)を設定します。縦向きディスプレイのアスペクト比が最大アスペクト比を超えると、ディスプレイの幅に関係なく、分割が無効になります。注: デフォルト値は 1.4 です。 アクティビティがタスク ウィンドウ全体を縦向きで占有する ほとんどのタブレットでは画面の向きが変わります関連項目 SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape()。横向きのデフォルト値は ALWAYS_ALLOW です。
      • setFinishPrimaryWithSecondary(): すべてのファイルをどのように終了させるかを設定します。 コンテナ内のアクティビティは、セカンダリ コンテナ内の 作成します。NEVER は、セカンダリ コンテナ内のすべてのアクティビティが終了したとき、システムはプライマリ アクティビティを終了してはならないことを示します(アクティビティを終了するを参照)。
      • setFinishSecondaryWithPrimary(): すべてのファイルをどのように終了させるかを設定します。 プライマリ コンテナ内のアクティビティが、 作成します。ALWAYS は、プライマリ コンテナ内のすべてのアクティビティが終了したときに、セカンダリ コンテナのアクティビティを常に終了させることを示します(アクティビティを終了するを参照)。
      • setClearTop(): プロジェクト内のすべてのアクティビティ 新しいアクティビティが起動されると、セカンダリ コンテナは終了します。 作成されます。false 値は、セカンダリ コンテナにすでに存在するアクティビティの上に新しいアクティビティが重ねられることを指定します。
    5. WindowManager の RuleController のシングルトン インスタンスを取得します。 ルールを追加します。

      Kotlin

        val ruleController = RuleController.getInstance(this)
        ruleController.addRule(splitPairRule)
        

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. セカンダリ コンテナのプレースホルダを作成する コンテンツがありません:

    1. 対象のアクティビティを識別する ActivityFilter を作成します。 プレースホルダは、次のようにタスク ウィンドウの分割を共有します。

      Kotlin

      val placeholderActivityFilter = ActivityFilter(
          ComponentName(this, ListActivity::class.java),
          null
      )

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. フィルタをフィルタセットに追加します。

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

      Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
      placeholderActivityFilterSet.add(placeholderActivityFilter);
    3. 作成: SplitPlaceholderRule:

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      SplitPlaceholderRule.Builder がルールを作成して設定します。

      • placeholderActivityFilterSet: 次のアクティビティ フィルタが含まれます。 ルールを適用するタイミングを判断するために、 プレースホルダのアクティビティが 関連付けられていることを確認します
      • Intent: プレースホルダ アクティビティの起動を指定します。
      • setDefaultSplitAttributes(): レイアウト属性をルールに適用します。
      • setMinWidthDp(): 最小表示幅(密度非依存ピクセル、dp)を設定します 分割することもできます
      • setMinSmallestWidthDp(): デバイスの向きにかかわらず分割を有効にするために、2 つのディスプレイ サイズのうち小さい方が必要とする最小値(dp)を設定します。
      • setMaxAspectRatioInPortrait(): アクティビティの分割が表示される縦向きの最大表示アスペクト比(高さ:幅)を設定します。注: デフォルト値は 1.4 です。これにより、ほとんどのタブレットでは、アクティビティがタスク ウィンドウに縦向きに表示されます。関連項目 SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULTsetMaxAspectRatioInLandscape()。 横向きのデフォルト値は ALWAYS_ALLOW です。
      • setFinishPrimaryWithPlaceholder(): プレースホルダ アクティビティを終了すると、プライマリ コンテナのアクティビティにどのように影響するかを設定します。ALWAYS は、システムが常に プレースホルダが呼び出されたときに、プライマリ コンテナのアクティビティを終了 終了させる(アクティビティを終了するを参照)。
      • setSticky(): プレースホルダのアクティビティが アクティビティ スタックの上に重ねて表示される プレースホルダが、十分な最小値を持つスプリットに初めて現れた あります。
    4. ルールを WindowManager RuleController に追加します。

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. 分割に入れないアクティビティを指定します。

    1. 実行するアクティビティを識別する ActivityFilter を作成する 常にタスクの表示領域全体を占有します。

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. フィルタをフィルタセットに追加します。

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. ActivityRule を作成します。

      Kotlin

      val activityRule = ActivityRule.Builder(expandedActivityFilterSet)
          .setAlwaysExpand(true)
          .build()

      Java

      ActivityRule activityRule = new ActivityRule.Builder(
          expandedActivityFilterSet
      ).setAlwaysExpand(true)
       .build();

      ActivityRule.Builder がルールを作成して設定します。

      • expandedActivityFilterSet: 次のアクティビティ フィルタが含まれます。 ルールを適用するタイミングを判断するために、 指定することもできます
      • setAlwaysExpand(): アクティビティを埋めるかどうかを指定します。 タスク ウィンドウ全体に表示されます。
    4. ルールを WindowManager RuleController に追加します。

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

アプリ間の埋め込み

Android 13(API レベル 33)以降では、アプリは他のアプリ 。アプリ間(UID 間)のアクティビティの埋め込み を使用すると、複数の Android アプリのアクティビティを視覚的に統合できます。単一アプリのアクティビティの埋め込みと同様に、ホストアプリのアクティビティと、別のアプリからの埋め込みアクティビティは、左右または上下に並べて画面上に表示されます。

たとえば、設定アプリは Google Chat の設定画面から壁紙セレクタ アクティビティを WallpaperPicker アプリ:

図 14. 設定アプリ(左のメニュー)。壁紙セレクタ アクティビティが埋め込まれている(右)。

信頼モデル

他のアプリからのアクティビティを埋め込むホストプロセスでは、埋め込みアクティビティの表示(サイズ、位置、切り抜き、透過性など)を再定義できます。悪意のあるホストがこの機能を使用して、ユーザーに誤解を与えたり、 クリックジャッキングなどの UI 偽装攻撃を作成する。

アプリ間のアクティビティの埋め込みの不正使用を防ぐため、Android ではアプリでのオプトインを必須としています アクティビティの埋め込みを可能にするためです。アプリは、ホストを信頼するか、信頼しないかを指定できます。

信頼するホスト

自身のアプリからアクティビティを埋め込み、その表示を完全に制御することを他のアプリに許可するには、<activity>android:knownActivityEmbeddingCerts 属性またはアプリのマニフェスト ファイルの <application> 要素にホストアプリの SHA-256 証明書を指定します。

android:knownActivityEmbeddingCerts の値を文字列として設定します。

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

または、文字列の配列で複数の証明書を指定します。

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

これは、次のようなリソースを参照します。

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

アプリ所有者は、Gradle signingReport タスクを実行することで SHA 証明書ダイジェストを取得できます。証明書ダイジェストは SHA-256 フィンガープリントで、 使用できます。詳しくは、署名レポートを実行するクライアントの認証をご覧ください。

信頼しないホスト

自身のアプリのアクティビティ埋め込みと、その表示の制御をすべてのアプリに許可するには、アプリ マニフェストの <activity> 要素または <application> 要素で android:allowUntrustedActivityEmbedding 属性を指定します。次に例を示します。

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

この属性のデフォルト値は false(アプリ間のアクティビティの埋め込みを禁止)です。

カスタム認証

信頼しないアクティビティの埋め込みのリスクを軽減するには、ホスト ID を検証するカスタム認証メカニズムを作成します。ホスト証明書がわかっている場合は、androidx.security.app.authenticator ライブラリを使用して認証します。アクティビティの埋め込み後にホストが認証すると、実際のコンテンツを表示できます。そうでない場合、アクションが許可されていないことをユーザーに知らせるとともに、コンテンツをブロックできます。

Jetpack WindowManager ライブラリの ActivityEmbeddingController#isActivityEmbedded() メソッドを使用して、ホストがアクティビティを埋め込んでいるかどうかを確認します。次に例を示します。

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

最小サイズの制限

Android システムでは、アプリで指定された最小の高さと幅が適用される マニフェストの <layout> 要素を埋め込みアクティビティに追加します。アプリで最小の高さと幅が指定されていない場合は、システムのデフォルト値が適用されます(sw220dp)。

ホストが埋め込みコンテナを最小サイズより小さいサイズに変更しようとすると、埋め込みコンテナのサイズは、タスク境界いっぱいにまで拡大します。

<activity-alias>

信頼する(またはしない)アクティビティの埋め込みを <activity-alias> 要素で機能させるには、android:knownActivityEmbeddingCerts または android:allowUntrustedActivityEmbedding を、エイリアスではなくそのアクティビティに適用する必要があります。システム サーバーのセキュリティを検証するポリシーは、 ターゲットに対して設定されたフラグに基づきます。

ホストアプリ

ホストアプリがアプリ間のアクティビティの埋め込みを実装する方法は、単一アプリの場合と同じです。SplitPairRuleSplitPairFilter オブジェクト、ActivityRule オブジェクト、ActivityFilter オブジェクト 埋め込みアクティビティとタスク ウィンドウの分割を指定できます。分割ルールは、XML で静的に、または Jetpack WindowManager API 呼び出しを使用して実行時に定義されます。

ホストアプリが、アプリ間の埋め込みをオプトインしていないアクティビティを埋め込もうとした場合、アクティビティはタスク境界いっぱいのスペースを占有します。その結果 ホストアプリは、ターゲット アクティビティがアプリ間をまたいで許可されるかどうかを把握する必要がある 説明します。

埋め込みアクティビティによって、同じタスク内で新しいアクティビティが開始され、 アプリ間の埋め込みを有効にしていない場合、アクティビティは 埋め込みコンテナ内のアクティビティをオーバーレイするのではなく、タスクの境界全体を使用します。

ホスト アプリケーションは、 アクティビティは 1 つのタスクで起動されます。

分割の例

フルウィンドウからの分割

図 15. アクティビティ A がアクティビティ B を横に起動。

リファクタリングは必要ありません。分割の構成を定義するには、 静的にまたはランタイム時に実行し、何も入力せずに Context#startActivity() を呼び出します。 パラメータを追加できます

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

デフォルトでの分割

アプリのランディング ページが、大画面で 2 つのコンテナに分割されるように設計されている場合、両方のアクティビティを同時に作成して表示すると最良のユーザー エクスペリエンスが得られます。ただし、ユーザーがプライマリ コンテナでアクティビティを操作する(ナビゲーション メニューからアイテムを選択するなど)まで、分割のセカンダリ コンテナでコンテンツを利用できない場合があります。プレースホルダ アクティビティは、コンテンツが表示されるまで空白を埋めることができます。 分割のセカンダリ コンテナに表示できます( プレースホルダ セクション)。

図 16. 2 つのアクティビティを同時に開くことで作成された分割。一方のアクティビティはプレースホルダです。

プレースホルダを伴う分割を作成するには、プレースホルダを作成し、プライマリ アクティビティに関連付けます。

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

アプリがインテントを受け取ると、ターゲット アクティビティをアクティビティ分割のセカンダリ部分として表示できます(リストのアイテムに関する情報を詳細画面に表示するリクエストなど)。詳細は、小さいディスプレイではタスク ウィンドウ全体に表示され、大きいデバイスではリストの横に表示されます。

図 17. ディープリンクの詳細アクティビティは、小画面では単独で表示され、大画面ではリスト アクティビティとともに表示される。

起動リクエストはメイン アクティビティにルーティングされ、ターゲットの詳細アクティビティは分割で起動する必要があります。システムは、利用可能な表示幅に基づいて、適切な表示方法(積み重ねまたは横並び)を自動的に選択します。

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

ディープリンクのデスティネーションだけが使用できるアクティビティである 「戻る」ナビゲーション スタックでユーザーにクリックされるのを防ぎます。また、 メイン アクティビティのみを残す場合は次のようになります。

リスト アクティビティと詳細アクティビティを並べて表示した大きなディスプレイ。
          「戻る」ナビゲーションで、詳細アクティビティを閉じてリスト アクティビティを画面に残すことはできません。

詳細アクティビティのみの小さいディスプレイ。「戻る」ナビゲーションで、詳細アクティビティを閉じてリスト アクティビティを表示することはできません。

代わりに、finishPrimaryWithSecondary 属性を使用することで両方のアクティビティを同時に終了できます。

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

構成属性のセクションをご覧ください。

分割コンテナ内の複数のアクティビティ

分割コンテナに複数のアクティビティを重ねると、ユーザーは深いコンテンツにアクセスできます。たとえば、リストと詳細の分割で、ユーザーがサブ詳細セクションに移動する必要がある場合でも、プライマリ アクティビティはそのまま表示し続けます。

図 18. タスク ウィンドウのセカンダリ ペインで所定の位置に開かれたアクティビティ。

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

サブ詳細アクティビティが、詳細アクティビティを隠すように、上に配置されます。

すると、ユーザーはスタック内をさかのぼって移動することで前の詳細レベルに戻ることができます。

図 19. スタック最上位からのアクティビティの削除。

アクティビティは、2 つのアクティビティの上に重ねて表示されます。 同じセカンダリ コンテナ内のアクティビティから起動される。作業内容 アクティブなスプリット内のプライマリ コンテナから起動され、 アクティビティ スタックの一番上にあるセカンダリ コンテナです。

新しいタスクのアクティビティ

分割タスク ウィンドウ内のアクティビティが新しいタスクのアクティビティを起動すると、新しいタスクは、分割を含むタスクとは別に、ウィンドウ全体に表示されます。履歴画面には 2 つのタスク(分割の中のタスクと新しいタスク)が表示されます。

図 20. アクティビティ B から新しいタスクでアクティビティ C を起動。

アクティビティの置き換え

セカンダリ コンテナ スタック内でアクティビティを置き換えることができます(プライマリ アクティビティをトップレベル ナビゲーションに使用し、セカンダリ アクティビティが選択したデスティネーションである場合など)。トップレベル ナビゲーションでの選択ごとに、 セカンダリ コンテナで新しいアクティビティを開始し、そのアクティビティを削除するか、 変更することもできます

図 21. プライマリ ペインのトップレベル ナビゲーション アクティビティが、セカンダリ ペインのデスティネーション アクティビティに置き換わる。

アプリがセカンダリ コンテナ内のアクティビティを終了しなかった場合、 ナビゲーションの選択が変更された場合、分割すると「戻る」ナビゲーションがわかりにくくなる場合があります 折りたたまれた状態(デバイスが折りたたまれた状態)たとえば、上部にメニューがあり、 セカンダリ ペインに重ねられた画面 A と画面 B は、ユーザーが スマートフォンを折りたたんだ状態。A の上に B、メニューの上に A があります。ユーザーが B から戻ると、メニューではなく A が表示されます。

このような場合は、画面 A をバックスタックから削除する必要があります。

既存の分割の上に新しいコンテナで横に起動する場合のデフォルト動作では、新しいセカンダリ コンテナが上に配置され、古いコンテナがバックスタックに保持されます。clearTop で以前のセカンダリ コンテナを消去して新しいアクティビティを通常どおり起動するように、分割を設定できます。

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

あるいは、同じセカンダリ アクティビティを使用し、プライマリ(メニュー)アクティビティから、同じインスタンスに解決されてセカンダリ コンテナで状態または UI の更新をトリガーする新しいインテントを送信します。

複数の分割

アプリで追加のアクティビティを起動することで、多層的なナビゲーションを提供できる 横にスワイプします

セカンダリ コンテナのアクティビティが新しいアクティビティを横に起動すると、 新しい分割が既存の分割の上に作成されます。

図 22. アクティビティ B がアクティビティ C を横に起動。

バックスタックには以前に開かれたすべてのアクティビティが含まれるため、ユーザーは C を終了した後に A/B スプリットに移動する。

スタック内にあるアクティビティ A、B、C。アクティビティは上から C、B、A の順に重ねられています。

新しい分割を作成するには、既存のセカンダリ コンテナから新しいアクティビティを横に起動します。A/B 分割と B/C 分割の両方の構成を宣言する B からアクティビティ C を通常どおり起動します。

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

分割状態の変更に対応する

アプリ内の異なるアクティビティに、同じ機能を果たす UI 要素が存在することがあります(アカウント設定を含むウィンドウを開くコントロールなど)。

図 23. 機能が同じ UI 要素を持つ異なるアクティビティ。

分割の中に共通の UI 要素を持つ 2 つのアクティビティがある場合、その要素を両方のアクティビティに表示することは冗長であり、混乱を招くおそれがあります。

図 24. アクティビティの分割で UI 要素が重複。

アクティビティが分割されるときを知るには、SplitController.splitInfoList フローを確認するか、分割状態の変化についてリスナーを SplitControllerCallbackAdapter に登録します。次に、それに応じて UI を調整します。

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

コルーチンはどのライフサイクル状態でも起動できますが、通常は リソースを節約するための STARTED 状態(Kotlin コルーチンを ライフサイクル対応コンポーネントをご覧ください)。

コールバックは、アクティビティが停止している場合を含め、どのようなライフサイクル状態でも発生する可能性があります。リスナーは通常、onStart() で登録し、未登録にします。 (onStop()

フルウィンドウ モーダル

ログイン画面、ポリシーの確認画面、エラー メッセージなど、アクティビティの中には、指定されたアクションが行われるまでユーザーがアプリを操作できないようにするものもあります。モーダル アクティビティは、分割に表示されないようにする必要があります。

Expand 設定を使用することで、アクティビティが常にタスク ウィンドウ全体に表示されるよう強制できます。

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

アクティビティを終了する

ディスプレイの端からスワイプすることで、分割のどちらかの側のアクティビティを終了できます。

図 25. スワイプ操作でアクティビティ B を終了。
図 26. スワイプ操作でアクティビティ A を終了。

デバイスがジェスチャー ナビゲーションではなく [戻る] ボタンを使用するよう設定されている場合、入力はフォーカスされているアクティビティ(最後にタップまたは起動されたアクティビティ)に送信されます。

コンテナ内のすべてのアクティビティを終了した場合、反対側のコンテナへの影響は分割の設定によって異なります。

構成属性

分割ペアのルール属性を指定して、そのペアの片方のアクティビティをすべて終了した場合、もう一方のアクティビティにどのように影響するかを構成できます。属性は次のとおりです。

  • window:finishPrimaryWithSecondary - セカンダリ コンテナ内のすべてのアクティビティを終了すると、プライマリ コンテナのアクティビティにどのように影響するか
  • window:finishSecondaryWithPrimary - プライマリ コンテナ内のすべてのアクティビティを終了すると、セカンダリ コンテナのアクティビティにどのように影響するか

指定可能な属性値は次のとおりです。

  • always - 関連付けられているコンテナのアクティビティを常に終了する
  • never - 関連付けられているコンテナのアクティビティを終了しない
  • adjacent - 次の時間が経過したら、関連付けられたコンテナのアクティビティを終了します。 2 つのコンテナは隣接して表示されますが、 2 つのコンテナがスタックされている

例:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

デフォルト設定

分割された 1 つのコンテナ内のすべてのアクティビティが終了すると、残りのコンテナがウィンドウ全体を占有します。

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

アクティビティ A とアクティビティ B を含む分割。A が終了すると B がウィンドウ全体を占有します。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A がウィンドウ全体を占有します。

アクティビティをまとめて終了する

すべてのアクティビティが同期されたら、プライマリ コンテナのアクティビティを自動的に終了する 作成します。

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

アクティビティ A とアクティビティ B を含む分割。B が終了すると A も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。A が終了すると B だけがタスク ウィンドウに残ります。

セカンダリ コンテナのアクティビティがすべて アクティビティの終了処理をトリガーします。

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

アクティビティ A とアクティビティ B を含む分割。A が終了すると B も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A だけがタスク ウィンドウに残ります。

プライマリまたはインスタンスのすべてのアクティビティが セカンダリ コンテナの終了:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

アクティビティ A とアクティビティ B を含む分割。A が終了すると B も終了し、タスク ウィンドウは空になります。

アクティビティ A とアクティビティ B を含む分割。B が終了すると A も終了し、タスク ウィンドウは空になります。

コンテナの複数のアクティビティを終了する

分割コンテナで複数のアクティビティが重なっている場合、その一番下にあるアクティビティが終了しても、上にあるアクティビティが自動的に終了することはありません。

たとえば、2 つのアクティビティがセカンダリ コンテナにあり、B の上に C があるとします。

アクティビティ B の上に重ねられたアクティビティ C を含むセカンダリ アクティビティ スタックが、アクティビティ A を含むプライマリ アクティビティ スタックの上に重ねられています。

分割の設定はアクティビティ A とアクティビティ B の設定によって定義されます。

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

一番上のアクティビティを終了しても分割は保持されます。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。C が終了すると、アクティビティ分割に A と B が残ります。

セカンダリ コンテナの下部(ルート)アクティビティを終了しても、削除されない その上のアクティビティを確認できます。分割も保持されます

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。B が終了すると、アクティビティ分割に A と C が残ります。

アクティビティをまとめて終了する場合の追加ルール( セカンダリ アクティビティとプライマリ アクティビティの両方が実行されます。

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。A が終了すると、B と C も終了します。

分割がプライマリとセカンダリをまとめて終了するように設定されている場合は次のようになります。

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。C が終了すると、アクティビティ分割に A と B が残ります。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。B が終了すると、アクティビティ分割に A と C が残ります。

プライマリ コンテナにアクティビティ A、セカンダリ コンテナにアクティビティ B と C を配置し、B の上に C を重ねた分割。A が終了すると、B と C も終了します。

ランタイムに分割プロパティを変更する

アクティブかつ表示されている分割のプロパティは変更できません。変更 分割ルールは追加のアクティビティ起動と新しいコンテナに影響しますが、 分割することもできます

アクティブな分割のプロパティを変更するには、横のアクティビティまたは分割の中のアクティビティを終了し、新しい設定でアクティビティを再び横に起動します。

動的分割のプロパティ

Jetpack WindowManager 1.4 がサポートしている Android 15(API レベル 35)以降 以上はアクティビティの構成性を可能にする動的な機能を提供 次のようなエンべディング分割

  • ペインの展開: インタラクティブでドラッグ可能な分割線を使用して、 分割プレゼンテーションのペインのサイズを変更する
  • アクティビティの固定: ユーザーは、1 つのコンテナ内のコンテンツを固定し、そのコンテナ内のナビゲーションを他のコンテナ内のナビゲーションから分離できます。
  • 全画面表示のダイアログを暗くする: ダイアログを表示するときに、アプリで以下の操作を指定できます。 タスク ウィンドウ全体を暗くするか、 クリックします。

ペインの展開

ペイン拡張を使用すると、割り当てられる画面スペースの大きさを調整できます デュアルペイン レイアウトで表示されます。

ウィンドウ分割線の外観をカスタマイズし、分割線のドラッグ可能な範囲を設定するには、次の操作を行います。

  1. DividerAttributes のインスタンスを作成する

  2. ディバイダの属性をカスタマイズします。

    • color: ドラッグ可能なペイン分割線の色。

    • widthDp: ドラッグ可能なペイン セパレータの幅。WIDTH_SYSTEM_DEFAULT に設定すると、システムが分割線の幅を決定します。

    • ドラッグ範囲: 画面全体のうち、各ペインに表示できる最小の割合 占有しがちです値の範囲は 0.33 ~ 0.66 です。DRAG_RANGE_SYSTEM_DEFAULT に設定すると、システムがドラッグ範囲を決定します。

Kotlin

val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)

if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    )
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()

Java

SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
    .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
    .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);

if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
    splitAttributesBuilder.setDividerAttributes(
      new DividerAttributes.DraggableDividerAttributes.Builder()
        .setColor(ContextCompat.getColor(context, R.color.divider_color))
        .setWidthDp(4)
        .setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
        .build()
    );
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();

アクティビティの固定

アクティビティの固定を使用すると、分割ウィンドウのいずれかを固定して、ユーザーが他のウィンドウ内で操作している間、アクティビティをそのまま維持できます。アクティビティの固定により、マルチタスク エクスペリエンスが向上します。

アプリでアクティビティの固定を有効にするには、次の操作を行います。

  1. 固定するアクティビティのレイアウト ファイルにボタンを追加し、 リストと詳細レイアウトの詳細アクティビティの例を以下に示します。

    <androidx.constraintlayout.widget.ConstraintLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/detailActivity"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="@color/white"
     tools:context=".DetailActivity">
    
    <TextView
       android:id="@+id/textViewItemDetail"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:textSize="36sp"
       android:textColor="@color/obsidian"
       app:layout_constraintBottom_toTopOf="@id/pinButton"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />
    
    <androidx.appcompat.widget.AppCompatButton
       android:id="@+id/pinButton"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/pin_this_activity"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. アクティビティの onCreate() メソッドで、ボタンに onclick リスナーを設定します。

    Kotlin

    pinButton = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        val splitAttributes: SplitAttributes = SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build()
    
        val pinSplitRule = SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build()
    
        SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) => {
        SplitAttributes splitAttributes = new SplitAttributes.Builder()
            .setSplitType(SplitAttributes.SplitType.ratio(0.66f))
            .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
            .build();
    
        SplitPinRule pinSplitRule = new SplitPinRule.Builder()
            .setSticky(true)
            .setDefaultSplitAttributes(splitAttributes)
            .build();
    
        SplitController.getInstance(getApplicationContext()).pinTopActivityStack(getTaskId(), pinSplitRule);
    });

全画面表示を暗くする

通常、アクティビティはディスプレイを暗くしてダイアログに注意を向けさせます。イン アクティビティの埋め込みでは、デュアルペイン ディスプレイの両ペインが暗くなり、 ダイアログを開いたアクティビティを含むペインのみが表示され、 体験できます

WindowManager 1.4 以降では、ダイアログが開くとデフォルトでアプリ ウィンドウ全体が暗くなります(EmbeddingConfiguration.DimAreaBehavior.ON_TASK を参照)。

ダイアログを開いたアクティビティのコンテナのみを暗くするには、次のコマンドを使用します。 EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK

分割からアクティビティを抽出してフルウィンドウにする

サイド アクティビティを全画面表示する新しい設定を作成する。次に、 同じインスタンスに解決されるインテントでアクティビティを再起動する。

ランタイムに分割のサポートを確認する

アクティビティの埋め込みは Android 12L(API レベル 32)以降でサポートされていますが、 以前のバージョンを実行している一部のデバイスでも利用できます。確認場所 使用できない場合は、kubectl の SplitController.splitSupportStatus プロパティまたは SplitController.getSplitSupportStatus() メソッド:

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

分割がサポートされていない場合、アクティビティはアクティビティ スタックの上で起動されます(非アクティビティ埋め込みモデルに従います)。

システムのオーバーライドを禁止する

Android デバイスのメーカー(相手先ブランド製品製造企業、OEM)は、デバイス システムの機能としてアクティビティの埋め込みを実装できます。システムは、アプリのウィンドウ処理動作をオーバーライドして、マルチアクティビティ アプリの分割ルールを指定します。システムによるオーバーライドが行われると、マルチアクティビティ アプリはシステム定義のアクティビティ埋め込みモードになります。

システムのアクティビティの埋め込みにより、アプリに変更を加えることなく、リスト詳細などのマルチペイン レイアウトを使用してアプリの表示を改善できます。ただし、誤ったレイアウトでアプリが表示されたり、バグや、アプリ側で実装するアクティビティ埋め込みとの競合を引き起こしたりすることもあります。

システム アクティビティの埋め込みを禁止または許可するには、 プロパティを宣言する必要があります。次に例を示します。

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"
            android:value="true|false" />
    </application>
</manifest>

プロパティ名は、Jetpack WindowManager の WindowProperties で定義します。 渡されます。アプリがアクティビティの埋め込みを実装している場合は、値を false に設定します。 システムがアクティビティの埋め込みを適用しないようにする場合 追加できます。システムによる適用を許可するには、値を true に設定します。 システム定義のアクティビティをアプリに埋め込むことができます。

制約、制限、注意事項

  • タスク内の他のアクティビティを整理したり、埋め込んだりできるのは、タスクのホストアプリ(タスクのルート アクティビティのオーナーとして識別)に限られます。埋め込みと分割をサポートするアクティビティが、別のアプリに属するタスクで実行される場合、それらのアクティビティでは埋め込みと分割が機能しません。
  • アクティビティは 1 つのタスク内でのみ整理できます。新しいタスクでアクティビティを起動すると、常に既存の分割の外部で新しく開いたウィンドウに配置されます。
  • 整理して分割に配置できるのは、同じプロセスのアクティビティに限られます。異なるプロセスのアクティビティを把握する方法がないため、SplitInfo コールバックは同じプロセスに属するアクティビティのみをレポートします。
  • 各ペアまたは単一のアクティビティ ルールは、ルールが登録された後に発生するアクティビティ起動にしか適用されません。現在のところ、既存の分割やその視覚的なプロパティを更新する方法はありません。
  • 分割ペアのフィルタ設定は、アクティビティを起動するときに使用するインテントと完全に一致する必要があります。この照合はアプリプロセスから新しいアクティビティが起動された時点で起こるため、暗黙的インテントを使用する場合、システム プロセスの後半で解決されるコンポーネント名が不明となることがあります。リリース時にコンポーネント名が不明な場合は、 代わりにワイルドカード("*/*")を使用すると、フィルタリングを実行できます。 アクションです
  • 現時点では、コンテナ間で、またはコンテナ内でアクティビティを移動する方法はありません。 削除されることはありません。分割は、ルールが一致する新しいアクティビティが起動したときにのみ、WindowManager ライブラリによって作成され、分割コンテナ内の最後のアクティビティが終了したときに破棄されます。
  • アクティビティは構成変更に伴って再起動される場合があるため、分割が作成または削除されてアクティビティの境界が変化すると、それまでのインスタンスが完全に破棄され新たなインスタンスが再作成される可能性があります。そのため、アプリ デベロッパーは、ライフサイクル コールバックから新しいアクティビティを起動する場合などに注意する必要があります。
  • アクティビティをサポートするには、デバイスに window extensions インターフェースが組み込まれている必要がある 説明します。Android 12L(API レベル)を搭載したほぼすべての大画面デバイス 32)以降にはインターフェースが含まれています。ただし、複数のアクティビティの実行に対応していない大画面デバイスには、window extensions インターフェースが組み込まれていない場合があります。大画面のデバイスがマルチウィンドウをサポートしていない場合 アクティビティの埋め込みをサポートしていない場合があります。

参考情報