デスクトップ ウィンドウ機能を使用すると、複数のアプリ ウィンドウのサイズ変更や位置変更をタブレット上でデスクトップと同じように行えます。
図 1 に、デスクトップ ウィンドウ機能が有効になっている場合の画面の構成を示します。注意事項:
- 複数のアプリを並べて同時に実行できます。
- タスクバーはディスプレイの下部に固定され、実行中のアプリが表示されます。アプリを固定してすばやくアクセスできます。
- 新しいカスタマイズ可能なヘッダーバーが各ウィンドウの上部に表示され、最小化や最大化などのコントロールが配置されます。
デフォルトでは、Android タブレットでアプリを開くと全画面表示になります。デスクトップ ウィンドウ機能でアプリを起動するには、画面上部のウィンドウ ハンドルを長押しして、図 2 のように UI 内でハンドルをドラッグします。
デスクトップ ウィンドウ機能でアプリを開くと、他のアプリもデスクトップ ウィンドウで開きます。
ハンドルをタップまたはクリックするか、キーボード ショートカット Meta キー(Windows、Command、または検索)+ Ctrl + 下を使用すると、 ウィンドウ ハンドルの下に表示されるメニューからデスクトップ ウィンドウ機能を呼び出すこともできます。
デスクトップ ウィンドウ機能を終了するには、アクティブなウィンドウをすべて閉じるか、デスクトップ ウィンドウの上部にあるウィンドウ ハンドルをつかんで、アプリを画面の上部にドラッグします。キーボード ショートカット Meta + H でもデスクトップ ウィンドウ機能を終了し、アプリを全画面表示で再度実行できます。
デスクトップ ウィンドウ機能に戻るには、[最近使ったアプリ] 画面でデスクトップ スペース タイルをタップまたはクリックします。
サイズ変更と互換モード
デスクトップ ウィンドウ機能では、画面の向きが固定されたアプリでも自由にサイズを変更できます。つまり、アクティビティが縦向きに固定されていても、アプリのサイズを変更して横向きのウィンドウにすることができます。
サイズ変更不可(resizeableActivity = false)として宣言されたアプリの UI は、アスペクト比を維持したままスケーリングされます。
画面の向きが固定されているか、サイズ変更不可として宣言されているカメラアプリでは、カメラのビューファインダーが特別に処理されます。ウィンドウのサイズは自由に調整できますが、ビューファインダーのアスペクト比は変わりません。アプリが常に縦向きまたは横向きで実行されることを前提として、プレビューやキャプチャした画像の向きやアスペクト比をハードコードしたり、誤った前提を置いたりすると、画像が引き伸ばされたり、横向きになったり、上下反転したりする可能性があります。
アプリが完全にレスポンシブなカメラ ビューファインダーを実装するまでは、この特別な処理により、誤った前提が原因で発生する可能性のある影響を軽減する、より基本的なユーザー エクスペリエンスを提供できます。
カメラアプリの互換モードについて詳しくは、デバイスの互換モードをご覧ください。
カスタマイズ可能なヘッダー インセット
デスクトップ ウィンドウ機能で実行されているすべてのアプリには、没入型 モードでもヘッダーバーが表示されます。このバーをカスタマイズして、アプリのコンテンツが隠れないようにしたり、カスタム UI 要素をヘッダー スペースに直接描画したりできます。
実装
ヘッダーバーにカスタム コンテンツを描画するには、まずヘッダーバーの背景を透明にする必要があります。これを行うには、
APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND フラグを
WindowInsetsController で使用します。
window.insetsController?.setSystemBarsAppearance( WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND, WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND )
ヘッダーバーが透明になったら、アプリのデザインに合わせてヘッダー領域のスタイルを設定できます。WindowInsets.isCaptionBarVisible を使用して、バーが
存在するかどうかを検出し、レイアウトに適切な高さまたはパディングを適用します。
@OptIn(ExperimentalLayoutApi::class) @Composable fun CaptionBar() { if (WindowInsets.isCaptionBarVisible) { Row( modifier = Modifier .windowInsetsTopHeight(WindowInsets.captionBar) .fillMaxWidth() .background(if (isSystemInDarkTheme()) Color.White else Color.Black), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically ) { Text( text = "Caption Bar Title", style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(4.dp) ) } } }
setSystemBarsAppearance(appearance,mask): システムバーのビジュアル スタイルを構成します。最初のパラメータはターゲットの表示フラグを定義し、2 番目のパラメータは変更する特定のフラグを制御するマスクとして機能します。windowInsetsTopHeight(): Composable の高さをシステムのヘッダーバーに合わせて自動的に設定します。これにより、カスタムの背景がピクセル値をハードコードすることなくキャプション領域を埋めることができます。WindowInsets.captionBar: デスクトップ ウィンドウ機能のコントロール([閉じる]、[最大化] など)の寸法を提供します。これにより、デスクトップ ウィンドウ機能の開始時または終了時に UI を自動的に スケーリングまたは非表示にできます。
詳細については、ウィンドウ インセットについてをご覧ください。タイトルに加えて、Google Chrome のタブ、検索バー、プロフィール アバターなど、他の UI 要素をキャプション バーに表示することもできます。
ユーザー インターフェース
UI がシステムボタンと重ならないようにするため、Android 15 では
WindowInsets#getBoundingRects() メソッドが用意されています。このメソッドは、システム要素が占有する領域を表す
Rectオブジェクトのリストを返します。キャプション バーの残りのスペースはセーフゾーンであり、カスタム コンテンツを安全に配置できます。
APPEARANCE_LIGHT_CAPTION_BARS を使用して、ライトテーマとダークテーマのシステム キャプション要素の表示を切り替えます。インセットにアクセスするには
WindowInsets.Companion.captionBar()、Compose では、または
WindowInsets.Type.captionBar() Views ではを使用します。
詳細については、ウィンドウ インセットについてをご覧ください。
マルチタスクとマルチインスタンスのサポート
マルチタスクはデスクトップ ウィンドウ機能の中核であり、アプリの複数のインスタンスを許可することで、ユーザーの生産性を大幅に向上させることができます。
Android 15 以降では、
PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI を使用できます。AndroidManifest.xml でこのプロパティを設定すると、アプリを複数のインスタンスで起動するためのオプション([新しいウィンドウ] ボタンなど)をシステム UI に提供するように指定できます。
<application>
<property
android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
android:value="true" />
</application>
注: デスクトップ ウィンドウ機能やその他のマルチウィンドウ環境では、新しいタスクは新しいウィンドウで開きます。そのため、アプリが複数のタスクを開始する場合は、ユーザー ジャーニーを再確認してください。
ドラッグ操作でアプリ インスタンスを管理する
マルチウィンドウ モードでは、アプリのウィンドウから UI 要素(タブやドキュメントなど)をドラッグして、新しいアプリ インスタンスを開始できます。同じアプリの異なるインスタンス間で要素を移動することもできます。
ドラッグ&ドロップでデータを転送する
ユーザーがアプリの別のインスタンスにコンテンツをドラッグしたり、画面の空の領域にコンテンツをドロップして新しいインスタンスを作成したりできるように、Composable をマルチインスタンスのドラッグ&ドロップのドラッグ元として構成するには、
新しいインスタンスを作成したり、画面の空の領域にコンテンツをドロップして新しいインスタンスを作成したりするには、
dragAndDropSource 修飾子を使用します。そのラムダで、転送するデータを含むClipDataと、マルチインスタンスの動作を構成するフラグを渡して、
DragAndDropTransferDataを返します。
Android 15 では、デスクトップ スタイルのウィンドウ機能とマルチインスタンスのインタラクション用に、次の 2 つの重要なフラグが導入されています。
DRAG_FLAG_GLOBAL_SAME_APPLICATION: ドラッグ操作がウィンドウの境界を越える可能性があることを示します(同じアプリの複数のインスタンスの場合)。このフラグを設定してstartDragAndDrop()が呼び出されると、同じアプリに属する 表示中のウィンドウのみが ドラッグ操作に参加し、ドラッグされたコンテンツを受け取ることができます。
Modifier.dragAndDropSource { _ -> DragAndDropTransferData( clipData = ClipData.newPlainText("label", "Your data"), flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION ) }
DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG: 他のウィンドウがドロップを処理しない場合、ユーザーはドラッグしたコンテンツを画面の空の領域にドロップして、アプリの新しいインスタンスを開始できます。- このフラグを使用する場合は、
IntentSenderを使用してClipData.Item.Builder#setIntentSender()を指定する必要があります。システムは、未処理のドロップが発生した場合に、この を使用して新しいアクティビティを起動します。
- このフラグを使用する場合は、
Modifier.dragAndDropSource { _ -> val intent = Intent.makeMainActivity(activity.componentName).apply { putExtra("EXTRA_ITEM_ID", itemId) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT } val pendingIntent = PendingIntent.getActivity( activity, 0, intent, PendingIntent.FLAG_IMMUTABLE ) val data = ClipData( "Item $itemId", arrayOf(ClipDescription.MIMETYPE_TEXT_INTENT), ClipData.Item.Builder().setIntentSender(pendingIntent.intentSender).build() ) DragAndDropTransferData( clipData = data, flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION or View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, ) }
転送されたデータを受け取る
別のインスタンスからデータを受け取るには、dragAndDropTarget 修飾子を使用します。
データが別のインスタンスまたはアプリから送信される場合は、権限を明示的にリクエストする必要があります。
Modifier.dragAndDropTarget( shouldStartDragAndDrop = { event -> event.toAndroidDragEvent().clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) }, target = object : DragAndDropTarget { override fun onDrop(event: DragAndDropEvent): Boolean { requestDragAndDropPermissions(activity, event.toAndroidDragEvent()) val clipData = event.toAndroidDragEvent().clipData val item = clipData?.getItemAt(0)?.text if (item != null) { // Process the dropped text item here } return item != null } } )
主な手順:
- フィルタ:
shouldStartDragAndDropを使用して、受信データ(MIME タイプ)がサポートされているかどうかを確認します。 - 権限:
requestDragAndDropPermissions(event)を呼び出して データにアクセスします。 - 処理:
onDropコールバックでデータを抽出します。
その他の最適化
アプリの起動をカスタマイズし、デスクトップ ウィンドウ機能から全画面表示にアプリを切り替えます。
デフォルトのサイズと位置を指定する
サイズ変更可能なアプリでも、ユーザーに価値を提供するために大きなウィンドウが必要とは限りません。メソッドを使用すると、アクティビティの起動時にデフォルトの
サイズと位置を指定できます。ActivityOptions#setLaunchBounds()
デスクトップ スペースから全画面表示に切り替える
アプリは Activity#requestFullScreenMode() を呼び出すことで全画面表示に切り替えることができます。このメソッドは、デスクトップ ウィンドウ機能からアプリを直接全画面表示します。