支援桌面視窗

有了「電腦分割視窗」功能,使用者可以同時執行多個應用程式,並調整應用程式視窗大小,享有桌機般的多元體驗。

圖 1 顯示啟用電腦視窗功能後,畫面的組織方式。注意事項:

  • 使用者可以同時並排執行多個應用程式。
  • 工作列固定在螢幕底部,顯示執行中的應用程式。使用者可以釘選應用程式,以便快速存取。
  • 全新可自訂的標題列會在每個視窗頂端顯示控制項,例如最小化和最大化。
平板電腦螢幕顯示多個應用程式在可調整大小的視窗中執行,底部則顯示工作列。
圖 1. 平板電腦上的電腦分割視窗。

根據預設,應用程式會在 Android 平板電腦上以全螢幕模式開啟。 如要在電腦分割視窗模式下啟動應用程式,請按住畫面頂端的視窗控點,然後在使用者介面中拖曳控點,如圖 2 所示。

如果應用程式在電腦分割視窗模式下開啟,其他應用程式也會在電腦視窗中開啟。

圖 2. 按住並拖曳應用程式視窗控點,進入桌面視窗模式。

使用者也可以透過選單叫用桌面視窗功能,方法是輕觸或按一下視窗控點,或使用鍵盤快速鍵 Meta 鍵 (Windows、Command 或搜尋) + Ctrl + 向下鍵,選單會顯示在視窗控點下方。

如要結束桌面視窗模式,使用者可以關閉所有作用中的視窗,或是抓住桌面視窗頂端的視窗控點,然後將應用程式拖曳到螢幕頂端。Meta + H 鍵盤快速鍵也會結束電腦分割視窗模式,並再次以全螢幕模式執行應用程式。

如要返回電腦視窗模式,請在「最近」畫面中輕觸或按一下電腦空間圖塊。

可調整大小和相容性模式

在電腦視窗模式下,螢幕方向鎖定的應用程式可自由調整大小。 也就是說,即使活動鎖定為直向,使用者仍可將應用程式大小調整為橫向視窗。

圖 3. 將僅限直向模式的應用程式視窗調整為橫向模式。

如果應用程式宣告為不可調整大小 (即 resizeableActivity = false),系統會縮放 UI,同時維持相同長寬比。

圖 4. 無法調整大小的應用程式 UI 會隨著視窗大小調整而縮放。

如果相機應用程式鎖定螢幕方向或聲明無法調整大小,系統會特別處理這類應用程式的相機觀景窗:視窗可完全調整大小,但觀景窗會維持相同長寬比。如果應用程式假設一律以直向或橫向模式執行,就會硬式編碼或做出其他假設,導致預覽或擷取的圖片方向或長寬比計算錯誤,造成圖片遭到延展、側向或倒置。

在應用程式準備好實作完全回應式相機觀景窗之前,特殊處理方式可提供更基本的使用者體驗,減輕錯誤假設可能造成的影響。

如要進一步瞭解相機應用程式的相容模式,請參閱「裝置相容模式」。

圖 5. 視窗大小調整時,相機觀景窗會保留顯示比例。

可自訂的標頭插邊

即使在沉浸模式下,以電腦視窗模式執行的所有應用程式都會有標題列。確認應用程式內容不會遭到標題列遮蓋。 標題列是插邊型說明文字列: WindowInsets.Companion.captionBar(); 在檢視區塊中,WindowInsets.Type.captionBar() 是系統資訊列的一部分。

如要進一步瞭解如何處理插邊,請參閱「在應用程式中以無邊框方式顯示內容,並在 Compose 中處理視窗插邊」。

標題列也可以自訂。Android 15 推出外觀類型 APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND,可將標題列設為透明,讓應用程式在標題列內繪製自訂內容。

應用程式隨後會負責設定內容頂端部分的樣式,使其看起來像說明文字列 (背景、自訂內容等),但系統說明文字元素 (關閉和最大化按鈕) 除外,這些元素是由系統在應用程式頂端的透明說明文字列上繪製。

應用程式可以使用 APPEARANCE_LIGHT_CAPTION_BARS,切換說明文字中系統元素在淺色和深色主題下的外觀,與切換狀態列和導覽列的方式類似。

Android 15 也推出了 WindowInsets#getBoundingRects() 方法,可讓應用程式更詳細地內省說明文字列插邊。應用程式可以區分系統繪製系統元素的區域,以及未使用的區域,應用程式可在這些區域放置自訂內容,不會與系統元素重疊。

API 傳回的 Rect 物件清單會指出應避免使用的系統區域。剩餘空間 (從說明文字列插邊減去矩形後計算得出) 是應用程式可繪製的空間,不會與系統元素重疊,且能夠接收輸入內容。

實作自訂標頭前後的 Chrome。
圖 6. 實作自訂標頭前後的 Chrome。

如要為自訂標頭設定系統手勢排除矩形,請在檢視區塊或可組合函式中實作下列項目:

// In a custom View's onLayout or a similar lifecycle method
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)
    if (changed) {
        // Calculate the height of your custom header
        val customHeaderHeight = 100 // Replace with your actual header height in pixels

        // Create a Rect covering your custom header area
        val exclusionRect = Rect(0, 0, width, customHeaderHeight)

        // Set the exclusion rects for the system
        systemGestureExclusionRects = listOf(exclusionRect)
    }
}

支援多工處理和多實體

多工處理是桌面視窗的核心功能,允許應用程式的多個執行個體可大幅提升使用者工作效率。

Android 15 推出 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI,應用程式可設定此屬性,指定要顯示的系統 UI,允許以多個執行個體啟動應用程式。

您可以在 <activity> 標記內的應用程式 AndroidManifest.xml 中宣告 PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI

<activity
    android:name=".MyActivity"
    android:exported="true"
    android:resizeableActivity="true">
    <meta-data
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</activity>

使用拖曳手勢管理應用程式執行個體

在多視窗模式下,使用者可以將檢視區塊元素拖曳出應用程式視窗,啟動新的應用程式例項。使用者也可以在同一個應用程式的不同執行個體之間移動元素。

圖 7. 將分頁拖曳出桌面視窗,即可啟動新的 Chrome 執行個體。

Android 15 導入兩個旗標,可自訂拖曳行為:

以下範例說明如何搭配 startDragAndDrop() 使用這些標記:

// Assuming 'view' is the View that initiates the drag
view.setOnLongClickListener {
    // Create an IntentSender for the activity you want to launch
    val launchIntent = Intent(view.context, NewInstanceActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(
        view.context,
        0,
        launchIntent,
        PendingIntent.FLAG_IMMUTABLE // Ensure the PendingIntent is immutable
    )

    // Build the ClipData.Item with the IntentSender
    val item = ClipData.Item.Builder()
        .setIntentSender(pendingIntent.intentSender)
        .build()

    // Create ClipData with a simple description and the item
    val dragData = ClipData(
        ClipDescription("New Instance Drag", arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)),
        item
    )

    // Combine the drag flags
    val dragFlags = View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG or
                    View.DRAG_FLAG_GLOBAL_SAME_APPLICATION

    // Start the drag operation
    view.startDragAndDrop(
        dragData,                     // The ClipData to drag
        View.DragShadowBuilder(view), // A visual representation of the dragged item
        null,                         // Local state object (not used here)
        dragFlags                     // The drag flags
    )
    true // Indicate that the long click was consumed
}
圖 8. 在兩個 Chrome 應用程式執行個體之間移動分頁。

其他最佳化

自訂應用程式啟動方式,以及從電腦分割視窗模式切換到全螢幕模式。

指定預設大小和位置

並非所有應用程式 (即使可調整大小) 都需要大視窗才能為使用者帶來價值。您可以使用 ActivityOptions#setLaunchBounds() 方法,在啟動活動時指定預設大小和位置。

以下範例說明如何設定活動的啟動界線:

val options = ActivityOptions.makeBasic()

// Define the desired launch bounds (left, top, right, bottom in pixels)
val launchBounds = Rect(100, 100, 700, 600) // Example: 600x500 window at (100,100)

// Apply the launch bounds to the ActivityOptions
options.setLaunchBounds(launchBounds)

// Start the activity with the specified options
val intent = Intent(this, MyActivity::class.java)
startActivity(intent, options.toBundle())

從桌面空間進入全螢幕模式

應用程式可以呼叫 Activity#requestFullScreenMode() 進入全螢幕模式。 這個方法會直接從桌面視窗顯示全螢幕應用程式。

如要從活動要求全螢幕模式,請使用下列程式碼:

// In an Activity
fun enterFullScreen() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // Android 15 (U)
        requestFullScreenMode()
    }
}