アクティビティの埋め込みは、大画面デバイス上のアプリのタスク ウィンドウを 2 つのアクティビティ、または同じアクティビティの 2 つのインスタンスに分割することでアプリを最適化する機能です。
複数のアクティビティで構成されているアプリの場合、アクティビティを埋め込むことで、タブレット、折りたたみ式デバイス、ChromeOS デバイスでのユーザー エクスペリエンスを向上させることができます。
アクティビティの埋め込みでは、コードのリファクタリングは必要ありません。XML 構成ファイルを作成するか、Jetpack WindowManager API 呼び出しを行うことで、アプリのアクティビティを並べて表示するか重ねて表示するかを決定します。
小画面のサポートは自動的に維持されます。アプリが小画面のデバイスにある場合、アクティビティは重ねられます。大画面の場合、アクティビティは並べて表示されます。作成した構成に基づいてシステムが表示方法を決定します。分岐ロジックは必要ありません。
アクティビティの埋め込みはデバイスの向きの変更に対応します。折りたたみ式デバイスでもシームレスに動作し、デバイスの折りたたみと展開に合わせて、アクティビティの積み重ねと積み重ね解除を行います。
アクティビティの埋め込みは、Android 12L(API レベル 32)以上を搭載するほとんどの大画面デバイスでサポートされています。
分割タスク ウィンドウ
アクティビティの埋め込みは、アプリのタスク ウィンドウをプライマリとセカンダリという 2 つのコンテナに分割します。コンテナは、メイン アクティビティから起動されたアクティビティ、またはコンテナ内にすでに存在する他のアクティビティから起動されたアクティビティを保持します。
アクティビティは、起動時にセカンダリ コンテナ内で重ねられ、小画面ではセカンダリ コンテナがプライマリ コンテナの上に重ねられるため、アクティビティの積み重ねと「戻る」ナビゲーションは、アプリにすでに組み込まれているアクティビティの順序と一致します。
アクティビティの埋め込みを使用すると、さまざまな方法でアクティビティを表示できます。アプリは、2 つのアクティビティを同時に並べて起動することで、タスク ウィンドウを分割できます。
また、タスク ウィンドウ全体を占有するアクティビティは、新しいアクティビティを並べて起動することで、タスク ウィンドウを分割できます。
すでに分割されてタスク ウィンドウを共有しているアクティビティは、次の方法で他のアクティビティを起動できます。
横のアクティビティの上に重ねて起動:
横に起動し、分割を横にシフトして以前のプライマリ アクティビティを非表示にする:
アクティビティをその場所の一番上(つまり同じアクティビティ スタック)に起動:
アクティビティをそのタスクのフルウィンドウで起動:
「戻る」ナビゲーション
アクティビティ間の依存関係やユーザーが「戻る」イベントをトリガーする方法に応じて、分割タスク ウィンドウ状態での「戻る」ナビゲーションのルールはアプリの種類によって異なる場合があります。次に例を示します。
- 一緒に: アクティビティ同士に関連性があり、一方のアクティビティを他方のアクティビティなしで表示すべきでない場合、両方を終了させるように「戻る」ナビゲーションを設定できます。
- 単独で: タスク ウィンドウ内のアクティビティ同士が完全に独立している場合、一方のアクティビティの「戻る」ナビゲーションは、他方のアクティビティの状態に影響を与えません。
ボタン ナビゲーションを使用する場合、「戻る」イベントは最後にフォーカスされたアクティビティに送信されます。
ジェスチャー ベースのナビゲーションの場合:
Android 14(API レベル 34)以前 - 「戻る」イベントは、ジェスチャーが発生したアクティビティに送信されます。ユーザーが画面の左側からスワイプすると、分割ウィンドウの左側のパネルにあるアクティビティに「戻る」イベントが送信されます。ユーザーが画面の右側からスワイプすると、右側のペインにあるアクティビティに「戻る」イベントが送信されます。
Android 15(API レベル 35)以降
同じアプリの複数のアクティビティを処理する場合、スワイプの方向に関係なく、ジェスチャーで最上位のアクティビティが終了するため、操作性の統一感が高まります。
異なるアプリの 2 つのアクティビティ(オーバーレイ)が関係するシナリオでは、ボタン ナビゲーションの動作に合わせて、最後にフォーカスされたアクティビティに「戻る」イベントが送信されます。
マルチペイン レイアウト
Jetpack WindowManager を使用すると、Android 12L(API レベル 32)以上を搭載した大画面デバイスと、以前のバージョンのプラットフォームを搭載した一部のデバイスで、マルチペイン レイアウトを埋め込むアクティビティを構築できます。フラグメントやビューベースのレイアウト(SlidingPaneLayout
など)ではなく複数のアクティビティに基づく既存のアプリは、ソースコードをリファクタリングすることなく、大画面のユーザー エクスペリエンスを向上させることができます。
一般的な例としては、リストと詳細の分割が挙げられます。質の高い表示を確保するために、システムはリスト アクティビティを起動し、その後アプリが直ちに詳細アクティビティを起動します。遷移システムは、両方のアクティビティが描画されるまで待機してから、一緒に表示します。ユーザーにとっては、2 つのアクティビティが 1 つのものとして起動します。
分割属性
分割されたコンテナ間でのタスク ウィンドウの比率と、コンテナ同士の相対的なレイアウトを指定できます。
XML 構成ファイルで定義されているルールについては、次の属性を設定します。
splitRatio
: コンテナの比率を設定します。値は、開区間(0.0、1.0)の浮動小数点数です。splitLayoutDirection
: 分割されたコンテナ同士を相対的にレイアウトする方法を指定します。次の値があります。ltr
: 左から右rtl
: 右から左locale
:ltr
またはrtl
のどちらかを、ロケール設定から決定します
例については、XML 構成のセクションをご覧ください。
WindowManager API を使用して作成されたルールについては、SplitAttributes.Builder
で SplitAttributes
オブジェクトを作成し、次のビルダー メソッドを呼び出します。
setSplitType()
: 分割されたコンテナの比率を設定します。SplitAttributes.SplitType.ratio()
メソッドなどの有効な引数については、SplitAttributes.SplitType
をご覧ください。setLayoutDirection()
: コンテナのレイアウトを設定します。設定可能な値については、SplitAttributes.LayoutDirection
をご覧ください。
例については、WindowManager API セクションをご覧ください。
プレースホルダ
プレースホルダ アクティビティは、アクティビティ分割の領域を占有する空のセカンダリ アクティビティです。最終的には、コンテンツを含む別のアクティビティに置き換えられます。たとえばプレースホルダ アクティビティは、リストのアイテムが選択されるまで、リストと詳細のレイアウトでアクティビティ分割のセカンダリ側を占有します。選択された時点で、プレースホルダは選択されたリストアイテムの詳細情報を含むアクティビティに置き換えられます。
デフォルトでは、プレースホルダは、アクティビティ分割に対応する十分なスペースがある場合にのみ表示されます。プレースホルダは、ディスプレイ サイズが分割を表示できないほど小さな幅または高さに変更されると自動的に終了します。スペースが許せば、再初期化した状態でプレースホルダは再起動されます。
ただし、SplitPlaceholder.Builder
の SplitPlaceholderRule
メソッドまたは setSticky()
メソッドの stickyPlaceholder
属性は、デフォルトの動作をオーバーライドできます。属性またはメソッドに true
の値が指定されている場合、ディスプレイを 2 ペイン ディスプレイから単一ペイン ディスプレイに縮小すると、システムにより、プレースホルダはタスク ウィンドウの一番上のアクティビティとして表示されます。(分割の設定で例をご覧ください)。
ウィンドウ サイズの変更
デバイスの設定が変更され、タスク ウィンドウの幅が狭くなり、マルチペイン レイアウトに十分な大きさでなくなった場合(大画面の折りたたみ式デバイスがタブレットサイズからスマートフォン サイズに折りたたまれたときや、マルチウィンドウ モードでアプリ ウィンドウのサイズが変更されたときなど)は、タスク ウィンドウのセカンダリ ペインにあるプレースホルダ以外のアクティビティが、プライマリ ペイン内のアクティビティの上に重ねて表示されます。
プレースホルダのアクティビティは、分割するために十分な表示幅がある場合にのみ表示されます。小画面では、プレースホルダは自動的に閉じられます。表示領域が再び十分な大きさになると、プレースホルダが再作成されます。(プレースホルダ セクションを参照)。
アクティビティを重ねることができるのは、WindowManager がセカンダリ ペインのアクティビティをプライマリ ペインのアクティビティの上に Z オーダーで表示するためです。
セカンダリ ペインの複数のアクティビティ
アクティビティ B が追加のインテント フラグなしで、アクティビティ C を所定の位置で起動します。
その結果、同じタスクのアクティビティの Z オーダーは次のようになります。
タスク ウィンドウが小さくなるとアプリは 1 つのアクティビティに縮小され、アクティビティ C がスタックの一番上に重ねられます。
小さいウィンドウで「戻る」操作を行うと、重ねられた各アクティビティ間で移動します。
タスク ウィンドウ構成を、複数のペインに対応できる大きなサイズに復元すると、アクティビティは再び並んで表示されます。
分割の積み重ね
アクティビティ B が、横にアクティビティ C を起動して分割を横にシフトします。
その結果、同じタスクのアクティビティの Z オーダーは次のようになります。
アプリは、タスク ウィンドウが小さくなると、アクティビティ C が一番上にある 1 つのアクティビティに縮小されます。
固定縦向き
android:screenOrientation マニフェスト設定を使用すると、アプリのアクティビティを縦向きまたは横向きに制限できます。タブレットや折りたたみ式デバイスなどの大画面デバイスでのユーザー エクスペリエンスを改善するために、デバイス メーカー(OEM)は画面の向きのリクエストを無視して、横向きディスプレイでアプリを縦向きに、縦向きディスプレイでアプリを横向きにレターボックス表示できます。
同様に、アクティビティの埋め込みが有効になっていると、OEM は大画面デバイス(幅 600 dp 以上)をカスタマイズして、横向き時に固定縦向きのアクティビティをレターボックス表示できます。固定縦向きアクティビティが 2 つ目のアクティビティを起動する場合、2 つのアクティビティを 2 つのペインで並べて表示できます。
必ずアプリのマニフェスト ファイルに android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
プロパティを追加して、アプリがアクティビティの埋め込みをサポートしていることをデバイスに伝えてください(分割の構成をご覧ください)。これにより、OEM によるカスタマイズがあるデバイスが、固定縦向きのアクティビティをレターボックス表示するかどうかを判断できます。
分割の設定
アクティビティ分割は、分割ルールによって設定します。分割ルールは、XML 構成ファイル内で定義するか、Jetpack WindowManager API 呼び出しを行って定義します。
どちらの方法を選択した場合でも、アプリから WindowManager ライブラリにアクセスし、アクティビティの埋め込みが実装されていることをシステムに通知する必要があります。
次のように対応します。
次の例のように、アプリのモジュール レベルの
build.gradle
ファイルに最新の WindowManager ライブラリ依存関係を追加します。implementation 'androidx.window:window:1.1.0-beta02'
WindowManager ライブラリは、アクティビティの埋め込みに必要なすべてのコンポーネントを提供します。
アプリがアクティビティの埋め込みを実装していることを、システムに通知します。
アプリ マニフェスト ファイルの <application> 要素に
android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED
プロパティを追加し、次の例のように値を 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 ベースで作成する手順は次のとおりです。
次の処理を行う 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>
イニシャライザを作成します。
WindowManager の
RuleController
コンポーネントは、XML 構成ファイルを解析してシステムで利用できるルールをつくります。Jetpack の Startup ライブラリInitializer
を使用すると、アプリの起動時に XML ファイルがRuleController
を利用できるようになり、アクティビティの開始時にルールが有効になります。イニシャライザを作成する手順は次のとおりです。
次の例のように、Jetpack Startup ライブラリの最新の依存関係をモジュール レベルの
build.gradle
ファイルに追加します。implementation 'androidx.startup:startup-runtime:1.1.1'
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(); } }
ルール定義のコンテンツ プロバイダを作成します。
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>
InitializationProvider
がSplitInitializer
を見つけて初期化してから、アプリのonCreate()
メソッドを呼び出します。そのため、分割ルールはアプリのメイン アクティビティが起動すると有効になります。
WindowManager API
アクティビティの埋め込みは、わずかな API 呼び出しを使用してプログラムで実装できます。Application
のサブクラスの onCreate()
メソッドで呼び出しを行い、アクティビティを起動する前にルールを有効にします。
アクティビティの分割をプログラムで作成する手順は、次のとおりです。
分割ルールを作成します。
分割を共有するアクティビティを識別するための
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 );
フィルタをフィルタセットに追加します。
Kotlin
val filterSet = setOf(splitPairFilter)
Java
Set<SplitPairFilter> filterSet = new HashSet<>(); filterSet.add(splitPairFilter);
分割のレイアウト属性を作成します。
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()
: プライマリ コンテナから順に、アクティビティ コンテナ同士の相対的なレイアウトを指定します。
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_DEFAULT
とsetMaxAspectRatioInLandscape()
もご覧ください。横向きのデフォルト値はALWAYS_ALLOW
です。setFinishPrimaryWithSecondary()
- セカンダリ コンテナ内のすべてのアクティビティを終了すると、プライマリ コンテナのアクティビティにどのように影響するかを設定します。NEVER
は、セカンダリ コンテナ内のすべてのアクティビティが終了したとき、システムはプライマリ アクティビティを終了してはならないことを示します(アクティビティを終了するを参照)。setFinishSecondaryWithPrimary()
- プライマリ コンテナ内のすべてのアクティビティを終了すると、セカンダリ コンテナのアクティビティにどのように影響するかを設定します。ALWAYS
は、プライマリ コンテナ内のすべてのアクティビティが終了したときに、セカンダリ コンテナ内のアクティビティを常に終了させることを示します(アクティビティを終了するを参照)。setClearTop()
: セカンダリ コンテナ内で新しいアクティビティが起動したときに、そのコンテナ内のすべてのアクティビティを終了するかどうかを指定します。false
値は、セカンダリ コンテナにすでに存在するアクティビティの上に新しいアクティビティが重ねられることを指定します。
WindowManager の
RuleController
のシングルトン インスタンスを取得し、ルールを追加します。Kotlin
val ruleController = RuleController.getInstance(this) ruleController.addRule(splitPairRule)
Java
RuleController ruleController = RuleController.getInstance(this); ruleController.addRule(splitPairRule);
コンテンツを利用できない場合に、セカンダリ コンテナのプレースホルダを作成します。
このプレースホルダが、タスク ウィンドウの分割をどのアクティビティと共有するかを識別するための
ActivityFilter
を作成します。Kotlin
val placeholderActivityFilter = ActivityFilter( ComponentName(this, ListActivity::class.java), null )
Java
ActivityFilter placeholderActivityFilter = new ActivityFilter( new ComponentName(this, ListActivity.class), null );
フィルタをフィルタセットに追加します。
Kotlin
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
Java
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>(); placeholderActivityFilterSet.add(placeholderActivityFilter);
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_DEFAULT
とsetMaxAspectRatioInLandscape()
もご覧ください。横向きのデフォルト値はALWAYS_ALLOW
です。setFinishPrimaryWithPlaceholder()
: プレースホルダ アクティビティを終了すると、プライマリ コンテナのアクティビティにどのように影響するかを設定します。ALWAYS は、プレースホルダが終了したときに、システムはプライマリ コンテナのアクティビティを常に終了させる必要があることを示します(アクティビティを終了するを参照)。setSticky()
: 十分な最小幅の分割でプレースホルダが最初に表示されたときに、小さなディスプレイのアクティビティ スタックの上にプレースホルダ アクティビティを表示するかどうかを決定します。
ルールを WindowManager
RuleController
に追加します。Kotlin
ruleController.addRule(splitPlaceholderRule)
Java
ruleController.addRule(splitPlaceholderRule);
分割に入れないアクティビティを指定します。
タスクの表示領域全体を常に占有するアクティビティを識別するための
ActivityFilter
を作成します。Kotlin
val expandedActivityFilter = ActivityFilter( ComponentName(this, ExpandedActivity::class.java), null )
Java
ActivityFilter expandedActivityFilter = new ActivityFilter( new ComponentName(this, ExpandedActivity.class), null );
フィルタをフィルタセットに追加します。
Kotlin
val expandedActivityFilterSet = setOf(expandedActivityFilter)
Java
Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>(); expandedActivityFilterSet.add(expandedActivityFilter);
ActivityRule
を作成します。Kotlin
val activityRule = ActivityRule.Builder(expandedActivityFilterSet) .setAlwaysExpand(true) .build()
Java
ActivityRule activityRule = new ActivityRule.Builder( expandedActivityFilterSet ).setAlwaysExpand(true) .build();
ActivityRule.Builder
がルールを作成して設定します。expandedActivityFilterSet
: 分割から除外するアクティビティを識別することで、いつルールを適用するかを判断するアクティビティ フィルタが含まれます。setAlwaysExpand()
: アクティビティをタスク ウィンドウ全体に表示するかどうかを指定します。
ルールを WindowManager
RuleController
に追加します。Kotlin
ruleController.addRule(activityRule)
Java
ruleController.addRule(activityRule);
アプリ間の埋め込み
Android 13(API レベル 33)以降では、他のアプリからアクティビティを埋め込むことができます。アプリ間(UID 間)のアクティビティ埋め込みを使用すると、複数の Android アプリから来るアクティビティを視覚的に統合できます。単一アプリのアクティビティの埋め込みと同様に、ホストアプリのアクティビティと、別のアプリからの埋め込みアクティビティは、左右または上下に並べて画面上に表示されます。
たとえば、設定アプリに、WallpaperPicker アプリの壁紙セレクタ アクティビティを埋め込むことができます。
信頼モデル
他のアプリからのアクティビティを埋め込むホストプロセスでは、埋め込みアクティビティの表示(サイズ、位置、切り抜き、透過性など)を再定義できます。悪意のあるホストはこの機能を利用して、ユーザーに誤解を与えたり、クリックジャッキングなどの 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
を、エイリアスではなくそのアクティビティに適用する必要があります。システム サーバー上でセキュリティを検証するポリシーは、エイリアスではなくそのアクティビティに設定されたフラグに基づきます。
ホストアプリ
ホストアプリがアプリ間のアクティビティの埋め込みを実装する方法は、単一アプリの場合と同じです。SplitPairRule
オブジェクトと SplitPairFilter
オブジェクト、または ActivityRule
オブジェクトと ActivityFilter
オブジェクトで、埋め込みアクティビティとタスク ウィンドウの分割を指定します。分割ルールは、XML で静的に、または Jetpack WindowManager API 呼び出しを使用して実行時に定義されます。
ホストアプリが、アプリ間の埋め込みをオプトインしていないアクティビティを埋め込もうとした場合、アクティビティはタスク境界いっぱいのスペースを占有します。そのため、ホストアプリは、ターゲット アクティビティでアプリ間の埋め込みが可能かどうかを確認する必要があります。
埋め込みアクティビティが同じタスク内で新しいアクティビティを起動し、新しいアクティビティでアプリ間埋め込みがオプトインされていない場合、新しいアクティビティは埋め込みコンテナでアクティビティをオーバーレイするのではなく、タスク境界いっぱいのスペースを占有します。
ホストアプリは、同じタスク内でアクティビティを起動する限り、制限なしで独自のアクティビティを埋め込むことができます。
分割の例
フルウィンドウからの分割
リファクタリングは必要ありません。分割の設定を静的に、またはランタイムに定義し、追加のパラメータなしで Context#startActivity()
を呼び出すことができます。
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
デフォルトでの分割
アプリのランディング ページが、大画面で 2 つのコンテナに分割されるように設計されている場合、両方のアクティビティを同時に作成して表示すると最良のユーザー エクスペリエンスが得られます。ただし、ユーザーがプライマリ コンテナでアクティビティを操作する(ナビゲーション メニューからアイテムを選択するなど)まで、分割のセカンダリ コンテナでコンテンツを利用できない場合があります。プレースホルダ アクティビティは、分割のセカンダリ コンテナにコンテンツを表示できるようになるまで、空白を埋めることができます(プレースホルダのセクションをご覧ください)。
プレースホルダを伴う分割を作成するには、プレースホルダを作成し、プライマリ アクティビティに関連付けます。
<SplitPlaceholderRule
window:placeholderActivityName=".PlaceholderActivity">
<ActivityFilter
window:activityName=".MainActivity"/>
</SplitPlaceholderRule>
ディープリンク分割
アプリがインテントを受け取ると、ターゲット アクティビティをアクティビティ分割のセカンダリ部分として表示できます(リストのアイテムに関する情報を詳細画面に表示するリクエストなど)。詳細は、小さいディスプレイではタスク ウィンドウ全体に表示され、大きいデバイスではリストの横に表示されます。
起動リクエストはメイン アクティビティにルーティングされ、ターゲットの詳細アクティビティは分割で起動する必要があります。システムは、利用可能な表示幅に基づいて、適切な表示方法(積み重ねまたは横並び)を自動的に選択します。
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>
構成属性セクションをご覧ください。
分割コンテナ内の複数のアクティビティ
分割コンテナに複数のアクティビティを重ねると、ユーザーは深いコンテンツにアクセスできます。たとえば、リストと詳細の分割で、ユーザーがサブ詳細セクションに移動する必要がある場合でも、プライマリ アクティビティはそのまま表示し続けます。
Kotlin
class DetailActivity { . . . fun onOpenSubDetail() { startActivity(Intent(this, SubDetailActivity::class.java)) } }
Java
public class DetailActivity { . . . void onOpenSubDetail() { startActivity(new Intent(this, SubDetailActivity.class)); } }
サブ詳細アクティビティが、詳細アクティビティを隠すように、上に配置されます。
すると、ユーザーはスタック内をさかのぼって移動することで前の詳細レベルに戻ることができます。
同じセカンダリ コンテナ内のアクティビティからアクティビティを起動すると、アクティビティは互いに重ねられます。これがデフォルトの動作です。アクティブな分割の中でプライマリ コンテナから起動されたアクティビティも、アクティビティ スタックの一番上にあるセカンダリ コンテナに配置されます。
新しいタスクのアクティビティ
分割タスク ウィンドウ内のアクティビティが新しいタスクのアクティビティを起動すると、新しいタスクは、分割を含むタスクとは別に、ウィンドウ全体に表示されます。履歴画面には 2 つのタスク(分割の中のタスクと新しいタスク)が表示されます。
アクティビティの置き換え
セカンダリ コンテナ スタック内でアクティビティを置き換えることができます(プライマリ アクティビティをトップレベル ナビゲーションに使用し、セカンダリ アクティビティが選択したデスティネーションである場合など)。トップレベル ナビゲーションから選択されるたびに、セカンダリ コンテナで新しいアクティビティが起動し、それまでそこにあったアクティビティは削除されます。
ナビゲーションの選択が変更されたときにアプリがセカンダリ コンテナのアクティビティを終了しない場合、分割が閉じられた(デバイスが折りたたまれた)ときに「戻る」ナビゲーションがわかりにくくなる可能性があります。たとえば、プライマリ ペインにメニューがあり、セカンダリ ペインに画面 A と B が重ねられている場合、ユーザーがスマートフォンを折りたたむと、B が A の上になり、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 の更新をトリガーする新しいインテントを送信します。
複数の分割
アプリは、追加のアクティビティを横に起動することで、多層の深いナビゲーションを提供できます。
セカンダリ コンテナのアクティビティが新しいアクティビティを横に起動すると、既存の分割の上に新しい分割が作成されます。
バックスタックには以前開かれたアクティビティがすべて含まれているため、ユーザーは C を終了した後に A/B 分割に移動できます。
新しい分割を作成するには、既存のセカンダリ コンテナから新しいアクティビティを横に起動します。A/B 分割と B/C 分割の両方の設定を宣言し、アクティビティ C を通常どおり B から起動します。
<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 要素が存在することがあります(アカウント設定を含むウィンドウを開くコントロールなど)。
分割の中に共通の UI 要素を持つ 2 つのアクティビティがある場合、その要素を両方のアクティビティに表示することは冗長であり、混乱を招くおそれがあります。
アクティビティが分割されるときを知るには、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>
アクティビティを終了する
ディスプレイの端からスワイプすることで、分割のどちらかの側のアクティビティを終了できます。
デバイスがジェスチャー ナビゲーションではなく [戻る] ボタンを使用するよう設定されている場合、入力はフォーカスされているアクティビティ(最後にタップまたは起動されたアクティビティ)に送信されます。
コンテナ内のすべてのアクティビティを終了した場合、反対側のコンテナへの影響は分割の設定によって異なります。
構成属性
分割ペアのルール属性を指定して、そのペアの片方のアクティビティをすべて終了した場合、もう一方のアクティビティにどのように影響するかを構成できます。属性は次のとおりです。
window:finishPrimaryWithSecondary
- セカンダリ コンテナ内のすべてのアクティビティを終了すると、プライマリ コンテナのアクティビティにどのように影響するかwindow:finishSecondaryWithPrimary
- プライマリ コンテナ内のすべてのアクティビティを終了すると、セカンダリ コンテナのアクティビティにどのように影響するか
指定可能な属性値は次のとおりです。
always
- 関連付けられているコンテナのアクティビティを常に終了するnever
- 関連付けられているコンテナのアクティビティを終了しないadjacent
- 2 つのコンテナが互いに隣接して表示される場合、関連付けられているコンテナのアクティビティを終了する(2 つのコンテナがスタックされている場合は終了しない)
例:
<SplitPairRule
<!-- Do not finish primary container activities when all secondary container activities finish. -->
window:finishPrimaryWithSecondary="never"
<!-- Finish secondary container activities when all primary container activities finish. -->
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
デフォルト設定
分割された 1 つのコンテナ内のすべてのアクティビティが終了すると、残りのコンテナがウィンドウ全体を占有します。
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
アクティビティをまとめて終了する
セカンダリ コンテナ内のすべてのアクティビティが終了すると、プライマリ コンテナのアクティビティは自動的に終了します。
<SplitPairRule
window:finishPrimaryWithSecondary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
プライマリ コンテナ内のすべてのアクティビティが終了すると、セカンダリ コンテナのアクティビティは自動的に終了します。
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
プライマリ コンテナ内またはセカンダリ コンテナ内のすべてのアクティビティが終了したときに、アクティビティをまとめて終了します。
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
コンテナの複数のアクティビティを終了する
分割コンテナで複数のアクティビティが重なっている場合、その一番下にあるアクティビティが終了しても、上にあるアクティビティが自動的に終了することはありません。
たとえば、2 つのアクティビティがセカンダリ コンテナにあり、B の上に C があるとします。
分割の設定はアクティビティ A とアクティビティ B の設定によって定義されます。
<SplitPairRule>
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
一番上のアクティビティを終了しても分割は保持されます。
セカンダリ コンテナの一番下(ルート)にあるアクティビティを終了しても、その上にあるアクティビティは削除されません。そのため、分割も保持されます。
プライマリ アクティビティとともにセカンダリ アクティビティを終了するなど、アクティビティをまとめて終了するための追加のルールも実施されます。
<SplitPairRule
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
分割がプライマリとセカンダリをまとめて終了するように設定されている場合は次のようになります。
<SplitPairRule
window:finishPrimaryWithSecondary="always"
window:finishSecondaryWithPrimary="always">
<SplitPairFilter
window:primaryActivityName=".A"
window:secondaryActivityName=".B"/>
</SplitPairRule>
ランタイムに分割プロパティを変更する
アクティブで表示されている分割のプロパティは変更できません。分割ルールの変更は、追加のアクティビティ起動と新しいコンテナに影響しますが、既存のアクティブな分割には影響しません。
アクティブな分割のプロパティを変更するには、横のアクティビティまたは分割の中のアクティビティを終了し、新しい設定でアクティビティを再び横に起動します。
動的分割プロパティ
Jetpack WindowManager 1.4 以降でサポートされている Android 15(API レベル 35)以降では、アクティビティの埋め込み分割の構成を可能にする動的機能が提供されています。たとえば、次のような機能があります。
- ペインの拡大: インタラクティブでドラッグ可能な分割線により、ユーザーは分割プレゼンテーションでペインのサイズを変更できます。
- アクティビティ スタックの固定: ユーザーは、1 つのコンテナ内のコンテンツを固定し、そのコンテナ内のナビゲーションを他のコンテナ内のナビゲーションから分離できます。
- ダイアログの全画面表示の暗くする: ダイアログを表示するときに、タスクウィンドウ全体を暗くするか、ダイアログを開いたコンテナのみを暗くするかをアプリで指定できます。
ペインの展開
ペインの展開を使用すると、デュアルペイン レイアウトの 2 つのアクティビティに割り当てられる画面スペースの量をユーザーが調整できます。
ウィンドウの分割線の外観をカスタマイズし、分割線のドラッグ可能な範囲を設定するには、次の操作を行います。
DividerAttributes
のインスタンスを作成する分割線の属性をカスタマイズします。
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();
アクティビティ スタックの固定
アクティビティ スタックの固定を使用すると、分割ウィンドウのいずれかを固定して、ユーザーが他方のウィンドウ内で操作している間もアクティビティをそのまま維持できます。アクティビティ スタックの固定により、マルチタスク エクスペリエンスが向上します。
アプリでアクティビティ スタックの固定を有効にするには、次の操作を行います。
固定するアクティビティのレイアウト ファイル(リストと詳細レイアウトの詳細アクティビティなど)にボタンを追加します。
<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>
アクティビティの
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); });
ダイアログの全画面表示を暗くする
通常、アクティビティはディスプレイを暗くしてダイアログに注意を向けさせます。アクティビティの埋め込みでは、統一された UI エクスペリエンスを実現するために、ダイアログを開いたアクティビティを含むペインだけでなく、デュアルペイン ディスプレイの両方のペインを暗くする必要があります。
WindowManager 1.4 以降では、ダイアログが開くと、デフォルトでアプリのウィンドウ全体が暗くなります(EmbeddingConfiguration.DimAreaBehavior.ON_TASK
を参照)。
ダイアログを開いたアクティビティのコンテナのみを暗くするには、EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK
を使用します。
分割からアクティビティを抽出してフルウィンドウにする
横のアクティビティをフルウィンドウ表示する新しい設定を作成してから、同じインスタンスに解決されるインテントでそのアクティビティを再起動します。
ランタイムに分割のサポートを確認する
アクティビティの埋め込みは Android 12L(API レベル 32)以上でサポートされますが、それ以前のプラットフォーム バージョンを搭載している一部のデバイスでも利用できます。この機能が利用可能かどうかをランタイムに確認するには、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 インターフェースが組み込まれていない場合があります。大画面デバイスがマルチウィンドウ モードをサポートしていない場合は、アクティビティの埋め込みがサポートされない可能性があります。
参考情報
- Codelab:
- 学習プログラム - アクティビティの埋め込み
- サンプルアプリ - activity-embedding