支援桌面視窗

電腦分割視窗可讓使用者在可調整大小的應用程式視窗中,同時執行多個應用程式,享有類似桌機的多功能體驗。

圖 1 顯示啟用電腦分割視窗模式後的畫面。注意事項:

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

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

當應用程式在電腦分割視窗中開啟時,其他應用程式也會在電腦分割視窗中開啟。

圖 2. 按住並拖曳應用程式視窗控點,即可進入電腦分割視窗模式。

使用者也可以輕觸或點選視窗控點,或使用鍵盤快速鍵 Meta 鍵 (Windows、Command 或搜尋) + Ctrl + 向下鍵,從視窗控點下方顯示的選單叫用電腦分割視窗。

使用者可以關閉所有使用中的視窗,或抓取電腦視窗頂端的視窗控點,並將應用程式拖曳到螢幕頂端,即可退出電腦分割視窗模式。Meta + H 鍵盤快速鍵也會退出電腦分割視窗,並再次以全螢幕模式執行應用程式。

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

針對類電腦環境最佳化應用程式版面配置

由於螢幕空間增加、滑鼠和鍵盤輸入的精確度,以及對高效率的期望,電腦版體驗的設計可能與行動版設計有很大差異。

Jetpack WindowManager 提供專屬 API,可協助開發人員決定何時顯示桌面 UI,這類 UI 通常具有較高的資訊密度、不同的導覽模式,以及最佳化的滑鼠互動。

lifecycleScope.launch(Dispatchers.Main) {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        windowInfoTracker.windowEngagementInfo(this@DesktopWindowingActivity)
            .collect { windowEngagementInfo ->
                if(windowEngagementInfo.hasEngagementMode(WindowEngagementInfo.EngagementMode.PRECISE_POINTER)){
                    showDesktopOptimizedUI()
                }else {
                    showTouchOptimizedUI()
                }
        }
    }
}

詳情請參閱「電腦版設計」。

可調整大小和相容模式

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

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

如果應用程式宣告為不可調整大小 (即 resizeableActivity = false),則 UI 會縮放,但保持相同的顯示比例。

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

如果相機應用程式鎖定螢幕方向,或宣告為不可調整大小,則相機觀景窗會經過特殊處理:視窗可完全調整大小,但觀景窗會保持相同的顯示比例。如果假設應用程式一律以直向或橫向模式執行,應用程式會硬式編碼或做出假設,導致預覽或擷取圖片的方向或顯示比例計算錯誤,進而造成圖片拉伸、側向或上下顛倒。

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

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

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

可自訂的標頭內嵌

在電腦分割視窗中執行的所有應用程式都有標題列,即使在沉浸模式下也是如此。您可以自訂這個列,避免應用程式內容遭到遮蓋,並直接在標頭空間中繪製自訂 UI 元素。

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

導入作業

如要在標頭列中繪製自訂內容,第一步是將標頭列背景設為透明。如要這麼做,請將 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):設定系統資訊列的視覺樣式。第一個參數定義目標外觀標記,第二個參數則做為遮罩,控制要修改哪些特定標記。

  • windowInsetsTopHeight():自動設定可組合項的高度,使其與系統的標頭列相符,讓自訂背景填滿標題區域,不需硬式編碼像素值。

  • WindowInsets.captionBar:提供電腦分割視窗控制項 (「關閉」、「最大化」等) 的尺寸,讓 UI 在進入或離開電腦分割視窗時自動縮放或隱藏。

詳情請參閱「關於視窗插邊」。除了標題外,您也可以在標題列中顯示其他 UI 元素,例如分頁 (如 Google Chrome)、搜尋列或個人資料顯示圖片。

使用者介面

為避免 UI 與系統按鈕重疊,Android 15 提供 WindowInsets#getBoundingRects() 方法。這個方法會傳回 Rect 物件清單,代表系統元素占用的區域。標題列中的任何剩餘空間都是安全區域,您可以在其中安全地放置自訂內容。

使用 APPEARANCE_LIGHT_CAPTION_BARS 切換淺色和深色主題的系統字幕元素外觀。在 Compose 中使用 WindowInsets.Companion.captionBar(),或在 View 中使用 WindowInsets.Type.captionBar(),即可存取插邊。

詳情請參閱「關於視窗內嵌」。

多工處理和多實體支援

多工處理是電腦分割視窗的核心功能,允許應用程式有多個執行個體,可大幅提高使用者的工作效率。

從 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 元素 (例如分頁或文件) 拖曳出應用程式視窗,藉此啟動新的應用程式執行個體。使用者也可以在同一個應用程式的不同執行個體之間移動元素。

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

使用拖放功能轉移資料

如要將可組合項設為多實體拖曳的拖曳來源,讓使用者將內容拖曳至應用程式的其他執行個體,或將內容拖曳至螢幕空白區域來建立「新」執行個體,請使用 dragAndDropSource 修飾符。在 lambda 中,傳回 DragAndDropTransferData,並傳遞包含要轉移資料的 ClipData,以及用於設定多實體行為的旗標。

Android 15 針對電腦模式視窗和多實體互動,推出兩項重要旗標:

  • DRAG_FLAG_GLOBAL_SAME_APPLICATION:表示拖曳作業可以跨越視窗邊界 (適用於同一應用程式的多個執行個體)。如果設定這個標記並呼叫 startDragAndDrop(),則只有屬於同一個應用程式的可見視窗,才能參與拖曳作業並接收拖曳內容。

Modifier.dragAndDropSource { _ ->
    DragAndDropTransferData(
        clipData = ClipData.newPlainText("label", "Your data"),
        flags = View.DRAG_FLAG_GLOBAL_SAME_APPLICATION
    )
}

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(),進入全螢幕模式。方法會直接從電腦分割視窗,以全螢幕顯示應用程式。