Compatibilidad con la renderización en ventanas de escritorio

窗口化模式使用户可以在可调整大小的应用窗口中同时运行多个应用,获享灵活多变、类似桌面设备的体验。

在图 1 中,您可以看到启用窗口化模式后屏幕的组织结构。需注意的事项:

  • 用户可以同时并排运行多个应用。
  • 任务栏位于显示屏底部的固定位置,用于显示正在运行的应用。用户可以固定应用以便快速访问。
  • 新的可自定义标题栏使用最小化和最大化等控件装饰每个窗口的顶部。
图 1.平板电脑上的窗口化模式。

默认情况下,应用会在 Android 平板电脑上以全屏模式打开。如需在窗口化模式下启动应用,请按住屏幕顶部的窗口手柄,然后在界面中拖动手柄,如图 2 所示。

当应用在窗口化模式下打开时,其他应用也会在桌面窗口中打开。

图 2. 按住并拖动应用窗口手柄,即可进入窗口化模式。

用户还可以通过以下方式调用窗口化模式:点按或点击手柄时,手柄下方会显示一个菜单;或者使用键盘快捷键 Meta 键(Windows、Command 或搜索)+ Ctrl + 向下键

用户可以通过关闭所有活跃窗口或抓取桌面窗口顶部的窗口手柄并将应用拖动到屏幕顶部来退出窗口化模式。Meta + H 键盘快捷键也会退出桌面 窗口化模式,并再次以全屏模式运行应用。

如需返回窗口化模式,请在“最近”屏幕中点按或点击桌面空间图块。

可调整大小和兼容性模式

在窗口化模式下,具有锁定屏幕方向的应用可以自由调整大小。这意味着,即使 activity 锁定为纵向屏幕方向,用户仍然可以将应用调整为横向窗口。

图 3. 将屏幕方向受限的应用的窗口调整为横向。

声明为不可调整大小的应用(即 resizeableActivity = false)的界面会 缩放,同时保持相同的宽高比。

图 4. 不可调整大小的应用的界面会随着窗口大小的调整而缩放。

对于锁定屏幕方向或声明为不可调整大小的相机应用,其相机取景器会受到特殊处理:窗口可以完全调整大小,但取景器会保持相同的宽高比。如果应用假定始终以纵向或横向模式运行,则应用会硬编码或以其他方式做出假设,从而导致预览或拍摄的图像屏幕方向或宽高比计算错误,导致图像拉伸、方向有误或倒置。

在应用准备好实现完全响应式相机取景器之前,特殊处理提供了一种更基本的用户体验,可减轻错误假设可能造成的影响。

如需详细了解相机应用的兼容性模式,请参阅设备 兼容性模式

图 5. 相机取景器在窗口调整大小时会保留其宽高比。

可自定义的标题插页式广告

在窗口化模式下运行的所有应用都有标题栏,即使在沉浸式 模式下也是如此。您可以自定义此栏,以防止应用的内容被遮盖,并直接在标题空间中绘制自定义界面元素。

实施自定义标头前后的 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():自动设置 Composable 的高度以匹配系统的标题栏,帮助自定义背景填充标题区域,而无需硬编码像素值。

  • WindowInsets.captionBar:提供桌面 窗口化模式控件(关闭最大化等)的尺寸,允许界面在进入或退出桌面窗口化模式时自动缩放或隐藏。

如需了解详情,请参阅窗口插页式广告简介。除了标题之外,您还可以在标题栏中显示其他界面元素,例如标签页(如 Google Chrome 中的标签页)、搜索栏或个人资料头像。

界面

为避免界面与系统按钮重叠,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 中设置此属性,您可以指定系统界面应提供选项(例如“新建窗口”按钮),以便在多个实例中启动应用。

<application>
    <property
        android:name="android.window.PROPERTY_SUPPORTS_MULTI_INSTANCE_SYSTEM_UI"
        android:value="true" />
</application>

注意 :在窗口化模式和其他多窗口模式环境中,新任务会在新窗口中打开,因此请在应用启动多个任务时仔细检查用户历程。

使用拖动手势管理应用实例

在多窗口模式下,用户可以通过将界面元素(例如标签页或文档)从应用窗口中拖出来启动新的应用实例。用户还可以在同一应用的不同实例之间移动元素。

图 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
        }
    }
)

关键步骤

其他优化

自定义应用启动,并将应用从窗口化模式转换为全屏模式。

指定默认大小和位置

并非所有应用(即使可调整大小)都需要大窗口才能提供用户价值。您 可以使用 ActivityOptions#setLaunchBounds() 方法在启动 activity 时指定默认 大小和位置。

从桌面空间进入全屏模式

应用可以通过调用 Activity#requestFullScreenMode() 进入全屏模式。该方法会直接从窗口化模式全屏显示应用。