窗口管理

ChromeOS 支持在多个窗口中运行 Android 应用。系统将应用渲染到窗口容器中,其大小由设备的尺寸决定,如图 1 所示。

图 1. 应用窗口在不同设备上的显示。

设计适用于不同屏幕尺寸的布局非常重要。如果您遵循 Android 指南支持不同屏幕尺寸,则您的应用在 ChromeOS 上运行时也能很好地工作。

本页面介绍了如何确保您的应用窗口正确启动、平滑调整大小,并在大小改变时显示其所有内容。

初始启动大小

应用可以通过以下方式请求其初始启动大小

  • 仅在桌面环境中使用启动大小。这有助于窗口管理器为您提供正确的边界和方向。要在桌面模式下使用时指定偏好设置,请在 <activity> 中添加以下元标记
<meta-data android:name="WindowManagerPreference:FreeformWindowSize"
           android:value="[phone|tablet|maximize]" />
<meta-data android:name="WindowManagerPreference:FreeformWindowOrientation"
           android:value="[portrait|landscape]" />
  • 使用静态启动边界。在您活动的清单条目中,使用 <layout> 指定一个“固定”的起始大小,如以下示例所示
<layout android:defaultHeight="500dp"
            android:defaultWidth="600dp"
            android:gravity="top|end"
            android:minHeight="450dp"
            android:minWidth="300dp" />
  • 使用动态启动边界。活动在创建新活动时可以使用并创建 ActivityOptions.setLaunchBounds(Rect)。通过指定空矩形,您的应用可以最大化。

调整窗口大小

在 ChromeOS 中,用户可以通过常规方式调整应用窗口大小:拖动右下角,如图 2 所示。

图 2. 一个可调整大小的应用窗口。

使用 View 类时,有两种处理窗口大小调整的选项

  • 通过调用 onConfigurationChanged(..) 动态响应配置更改。例如,您可以将 android:configChanges="screenSize|smallestScreenSize|orientation|screenLayout" 添加到活动的清单中。有关处理配置更改的更多信息,请阅读处理配置更改
  • 让系统重新启动活动。在这种情况下,请实现 onSaveInstanceState 并使用 ViewModel 架构组件来恢复之前保存的状态。

使用 Jetpack Compose 时,调整大小的行为取决于您的活动配置方式。如果它动态处理更改,则当窗口大小改变时会触发重新组合。如果活动由系统重新启动,则在重新启动后会发生初始组合。无论哪种方式,创建能够适应不断变化的窗口大小的 Compose 布局都非常重要。不要假设固定大小。

窗口尺寸

让您的活动每次启动时读取其窗口尺寸,并根据当前配置排列其内容。

要确定当前配置,请在当前活动上调用 getResources().getConfiguration()。不要使用后台活动或系统资源的配置。后台活动没有大小,并且系统配置可能包含多个具有冲突大小和方向的窗口,因此无法提取可用数据。

请注意,窗口大小和屏幕大小是不同的。要以 DP 为单位获取窗口大小,请使用 Activity.getResources().getConfiguration().screenWidthActivity.getResources().getConfiguration().screenHeight。您可能永远不需要使用屏幕大小。

内容边界

窗口的内容边界在调整大小后可能会改变。例如,如果窗口变得太大而无法在屏幕上容纳,则应用使用的窗口内区域可能会改变。请遵循以下指南

  • 使用 Android 布局过程的应用会自动在可用空间中布局。
  • 原生应用需要读取可用区域并监视大小变化,以避免出现无法访问的 UI 元素。调用以下方法来确定此界面的初始可用大小

    • NativeActivity.mLastContent[X/Y/Width/Height]()
    • findViewById(android.R.id.content).get[Width/Height]()

    可以使用观察者进行持续监控

    • NativeActivity.onContentRectChangedNative()
    • NativeActivity.onGlobalLayout()
    • view.addOnLayoutChangeListener(findViewById(android.R.id.content)) 添加一个监听器

    如果应用预缩放其图像,请在每次分辨率更改时执行此操作。

自由形式调整大小

ChromeOS 允许任何窗口自由调整大小:用户可以更改窗口的宽度、高度和在屏幕上的位置。许多 Android 应用在编写时没有考虑自由形式调整大小。请考虑以下问题

  • 屏幕位置可能会改变。始终使用系统执行窗口到屏幕和屏幕到窗口的坐标转换。
  • 如果您使用 Android 的视图系统,当窗口大小改变时,您的窗口布局会自动改变。
  • 如果您不使用视图系统并接管界面,您的应用必须自行处理大小更改。
  • 对于原生应用,请使用 mLastContent 成员或使用内容视图来确定初始大小。
  • 当应用运行时,监听 onContentRectChangedNativeonGlobalLayout 事件以响应大小变化。
  • 当应用大小改变时,重新缩放或重新加载布局和图像,并更新输入区域。

全屏模式

全屏模式的工作方式与原生 Android 相同。如果窗口没有覆盖整个屏幕,则全屏请求(隐藏所有系统 UI 元素)将被忽略。当应用最大化时,将执行正常的全屏方法、布局和功能。这会隐藏系统 UI 元素(窗口控制栏和任务栏)。

屏幕方向

Android 应用最常见的方向是纵向,因为大多数手机都是这样握持的。虽然纵向适用于手机,但对于笔记本电脑和平板电脑来说却很糟糕,这些设备更倾向于横向。为了让您的应用获得最佳效果,请考虑同时支持这两种方向。

有些 Android 应用假定当设备以纵向模式握持时,旋转值为 Surface.ROTATION_0。这对于大多数 Android 设备可能成立。然而,当应用处于某种 ARC 模式时,纵向的旋转值可能不是 Surface.ROTATION_0

为了在读取加速度计或类似传感器时获得准确的旋转值,请使用 Display.getRotation() 方法并相应地交换轴。

根活动与方向

Chromebook 窗口由活动窗口堆栈组成。堆栈中的每个窗口都具有相同的大小和方向。

在桌面环境中,突然的方向和大小变化会令人困惑。Chromebook 窗口管理器通过类似于 Android 的分屏模式的方式避免了这种情况:堆栈底部的活动控制其上方所有活动的属性。这可能导致意想不到的情况,即新启动的纵向且不可调整大小的活动变为横向且可调整大小。

设备模式在此处产生影响:在平板电脑模式下,方向不锁定,每个窗口保留自己的方向,这与 Android 上的正常情况一样。

方向指南

请遵循以下处理方向的指南

  • 如果您只支持一种方向,请将信息添加到清单中,以便窗口管理器在启动应用之前了解它。指定方向时,如果可能,也请指定传感器方向。Chromebook 通常是可变形的,倒置的应用会带来糟糕的用户体验。
  • 尝试保持单一选定方向。避免在清单中请求一个方向,然后稍后以编程方式设置另一个方向。
  • 谨慎根据窗口大小更改方向。用户可能会被困在一个小的纵向窗口中,无法返回更大的横向窗口。
  • Chrome 中有窗口控件,可在所有可用布局之间切换。通过选择正确的方向选项,您可以确保用户在启动应用后拥有正确的布局。如果应用同时支持纵向和横向,请尽可能将其默认设置为横向。设置此选项后,它会按应用记住。
  • 尽量避免不必要的方向更改。例如,如果活动方向是纵向,但应用在运行时调用 setRequestedOrientation(LANDSCAPE),这会导致不必要的窗口大小调整,这会令用户感到烦恼,并且如果应用无法处理,可能会导致应用重启。最好一次性设置方向(例如在清单中),并且仅在必要时才更改。

其他注意事项

在 ChromeOS 中使用 Android 应用时,还需要考虑以下事项

  • 不要在活动的 onDestroy 方法中调用 finish()。这会导致应用在调整大小时关闭而不是重新启动。
  • 不要使用不兼容的窗口类型,例如 TYPE_KEYGUARDTYPE_APPLICATION_MEDIA
  • 通过缓存以前分配的对象来加快活动重启速度。
  • 如果您不希望用户调整您的应用大小,请在清单文件中指定 android:resizeableActivity=false
  • 测试您的应用,确保它能适当地处理窗口大小的变化。