将您的 UI 迁移到响应式布局

Android 应用需要支持不断扩展的设备外形尺寸生态系统。应用的 UI 应具有响应能力,以适应各种屏幕尺寸以及不同的方向和设备状态。

响应式 UI 以灵活性和连续性原则为中心。

灵活性是指布局能够最大程度地利用可用空间,并在可用空间发生变化时进行调整。调整可以采用多种形式:简单地增加单个视图的大小,重新定位视图使其位于更易访问的位置,显示或隐藏其他视图,或将这些操作组合起来。

连续性是指在从一个窗口大小转换到另一个窗口大小时拥有无缝的用户体验。用户参与的任何体验都应无中断地继续。由于大小变化可能会伴随着整个视图层次结构的销毁和重新创建,因此重要的是用户不会丢失其位置或数据。

需要避免的事项

避免使用物理硬件值来做出布局决策。根据固定值做出决策可能很诱人,但在许多情况下,这些值对于确定 UI 可以使用的空间并没有用。

应用在多窗口模式、画中画或 Chrome OS 等自由形式窗口中运行时可能会遇到窗口大小调整。甚至可能存在多个物理屏幕,例如可折叠设备或具有多个显示屏的设备。在所有这些情况下,物理屏幕尺寸与决定如何显示内容无关。

Multiple devices showing app windows of different sizes.
图 1. 窗口大小可能与物理设备或显示器大小不同。

出于同样的原因,避免将应用锁定到特定方向或纵横比。虽然设备本身可能处于特定方向,但应用的方向可能仅根据其窗口的大小而有所不同。例如,在平板电脑横向模式下使用多窗口模式时,应用可能处于纵向模式,因为它的高度大于宽度。

此外,避免尝试确定设备是手机还是平板电脑。什么具体算作平板电脑在某种程度上是主观的:是基于一定的大小、纵横比还是大小和纵横比的组合?随着新外形尺寸的出现,这些假设可能会发生变化,并且这种区别变得不那么重要。

不要尝试任何上述策略,而是使用断点和窗口大小类。

断点和窗口大小类

分配给应用的屏幕的实际部分是应用的窗口。它可能占据整个屏幕或屏幕的一部分,因此在做出有关应用布局的高级决策时,请使用窗口大小。

在为多个外形尺寸设计时,找到这些高级决策向不同方向分支的阈值。为此,Material Design 响应式布局网格 提供了宽度和高度的断点,使您可以将原始尺寸映射到称为窗口大小类的离散标准化组。由于垂直滚动的普遍性,大多数应用主要关心宽度大小类,因此大多数应用可以通过处理几个断点来针对所有屏幕尺寸进行优化。(有关窗口大小类的更多信息,请参阅 使用窗口大小类。)

持久性 UI 元素

Material Design 布局指南 定义了应用栏、导航和内容的区域。通常,前两个是视图层次结构根部(或非常靠近根部)的持久性 UI 元素。请注意,“持久性”并不一定意味着视图始终可见,而是指在其他内容视图可能会移动或更改时,它会保持原位。例如,导航元素可能位于屏幕外的滑动抽屉中,但抽屉始终存在。

持久性元素可以是响应式的,并且通常占据窗口的全部宽度或全部高度,因此更倾向于使用大小类来决定放置它们的位置。这划定了留给内容的空间。在以下代码片段中,活动对紧凑型屏幕使用底部栏,对较大的屏幕使用顶部应用栏。限定布局使用前面描述的宽度断点。

<!-- res/layout/main_activity.xml -->

<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- content view(s) -->

    <com.google.android.material.bottomappbar.BottomAppBar
        android:layout_width="wrap_content"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        ... />
</androidx.constraintlayout.widget.ConstraintLayout>


<!-- res/layout-w600dp/main_activity.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        ... />

    <!-- content view(s) -->
</androidx.constraintlayout.widget.ConstraintLayout>

内容

放置好持久性 UI 元素后,使用剩余空间放置内容,例如通过使用带有应用导航图的 NavHostFragment。有关其他注意事项,请参阅 响应式 UI 的导航

确保所有数据都可用于不同的大小

如今,大多数应用程序框架都使用与贡献 UI 的 Android 组件(Activity、Fragment 和 View)分离的数据模型。使用 Jetpack 时,此角色通常由 ViewModel 承担,ViewModel 具有在配置更改中保持存活的额外优势(有关更多信息,请参阅ViewModel 概述)。

在实现适应不同尺寸的布局时,可能很想根据当前尺寸使用不同的数据模型。但是,这违反了单向数据流的原则。数据应向下流动到 View,而用户交互等事件应向上流动。在数据模型依赖于 UI 层配置的情况下创建反向依赖关系会大大增加复杂性。当应用程序尺寸更改时,您必须考虑从一个数据模型转换为另一个数据模型。

相反,让您的数据模型适应最大的尺寸类别,然后您可以有选择地在 UI 中显示、隐藏或重新定位内容以适应当前的尺寸类别。以下是一些在决定布局在尺寸类别之间转换时的行为方式时可以使用的一些策略。

扩展内容

规范布局:Feed

扩展的空间可以是一个机会,可以简单地使内容更大并重新格式化内容,使其更易于访问。

使集合更大。许多应用程序在滚动容器(例如 RecyclerViewScrollView)中显示项目集合。使容器能够自动变大意味着可以显示更多内容。但是,请注意容器内的内容不会过度拉伸或变形。例如,对于 RecyclerView,当宽度不紧凑时,请考虑使用不同的布局管理器,例如 GridLayoutManagerStaggeredGridLayoutManagerFlexboxLayout

A device folded and unfolded showing how different layout managers lay out the app differently based on width size class.
图 2. 不同窗口尺寸类别的不同布局管理器。

各个项目也可以利用不同的尺寸或形状来显示更多内容并更容易区分项目边界。

强调英雄元素。如果布局有一个特定的焦点,例如图像或视频,则在应用程序窗口增大时将其扩展以保持用户的注意力。其他辅助元素可以围绕或位于英雄视图下方重新排列。

构建此类布局的方法有很多,但 ConstraintLayout 特别适合此目的,因为它提供了许多方法来约束子视图的尺寸(包括按百分比或强制纵横比)以及相对于自身或其他子视图定位其子视图。在使用 ConstraintLayout 构建响应式 UI中了解所有这些功能的更多信息。

默认显示可折叠内容。当有可用空间时,公开原本只能通过其他用户交互(如点击、滚动或手势)访问的内容。例如,在紧凑模式下出现在选项卡界面中的内容可以在有更多可用空间时重新排列成列或列表。

扩展边距。如果空间很大,即使在利用了所有内容后也找不到合适的布局,则扩展布局的边距,以便内容保持居中,并且各个视图具有自然的尺寸和它们之间的间距。

或者,全屏组件可以转换为浮动对话框 UI。当该组件需要独占焦点来完成即时用户任务(例如撰写电子邮件或创建日历事件)时,这尤其适用。

Standard phone showing a dialog full screen, and an unfolded foldable phone showing the same dialog as a floating window.
图 3. 全屏对话框在中尺寸和扩展宽度下转换为标准对话框。

添加内容

规范布局:辅助窗格、列表-详细信息视图

使用辅助窗格。辅助窗格显示与主要内容相关的其他内容或上下文操作,例如文档中的评论或播放列表中的项目。通常,它们使用屏幕底部的三分之一用于扩展高度或尾部的三分之一用于扩展宽度。

一个重要的考虑因素是在没有足够空间显示窗格时将此内容放置在哪里。以下是一些可供探索的替代方案

  • 使用 DrawerLayout 在尾部边缘显示侧边抽屉
  • 使用 BottomSheetBehavior 显示底部抽屉
  • 通过点击菜单图标访问菜单或弹出窗口
图 4. 在辅助窗格中呈现其他内容的替代方法。

创建双窗格布局。大屏幕可能会显示通常在较小屏幕上单独显示的功能组合。许多应用程序中常见的交互模式是显示项目列表(如联系人或搜索结果),并在选择项目时切换到项目的详细信息。与其为较大屏幕放大列表,不如使用列表-详细信息视图在双窗格布局中并排显示这两个功能。与辅助窗格不同,列表-详细信息视图的详细信息窗格是一个独立的元素,可以在较小屏幕上独立显示。

使用 SlidingPaneLayout 专用小部件来实现列表-详细信息视图。此小部件会根据为这两个窗格指定的 layout_width 值自动计算是否有足够的空间将两个窗格一起显示,并且可以使用 layout_weight 分配任何剩余的空间。当没有足够的空间时,每个窗格都使用布局的完整宽度,并且详细信息窗格要么滑出屏幕,要么滑到列表窗格的顶部。

SlidingPaneLayout showing both panes of a list-detail layout on a device with a wide display.
图 5. SlidingPaneLayout 在扩展宽度下显示两个窗格,在紧凑宽度下显示一个窗格。

创建双窗格布局 包含有关使用 SlidingPaneLayout 的更多详细信息。另请注意,此模式可能会影响您构建导航图的方式(请参阅响应式 UI 的导航)。

其他资源