将界面迁移到响应式布局

Android 应用需要支持不断扩大的设备外形规格生态系统。应用界面应该是响应式的,能够适应各种屏幕尺寸以及不同的屏幕方向和设备状态。

响应式界面的核心在于灵活性和连续性。

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

连续性是指在从一种窗口大小转换为另一种窗口大小时带来无缝的用户体验。无论用户置身于何种体验,该体验都应能继续进行而不中断。由于大小的变化可能会伴随着整个视图层次结构的销毁和重新创建,因此务必要确保用户不会丢失其位置或数据。

需要避免的事项

避免根据物理硬件值来确定布局。您可能很想根据固定值来确定布局,但在许多情况下,这些值对于确定界面可使用的空间来说没有用处。

在平板电脑上,应用可能会在多窗口模式下运行,也就是说,该应用会与另一个应用共享屏幕。在 ChromeOS 中,应用可能会位于可调整大小的窗口中。甚至可能会有多个物理屏幕,例如可折叠设备或配备了多个显示屏的设备。在所有这些情况下,物理屏幕的尺寸与决定如何显示内容并不相关。

多个设备,显示了不同大小的应用窗口。
图 1. 窗口大小可能不同于实体设备或显示屏的尺寸。

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

此外,还应避免尝试确定设备属于手机还是平板电脑。确定设备属于平板电脑的具体标准多少有些主观:是必须具备一定的大小还是宽高比,亦或是二者兼备呢?随着新外形规格的出现,这些假设可能会改变,因此这种区别就失去了重要性。

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

断点和窗口大小类

分配给应用的实际屏幕区域即为应用的窗口。该区域可能会占据整个屏幕或屏幕的一部分,因此在确定应用的大体布局时应使用窗口大小。

针对多种外形规格进行设计时,应找到确定的大体布局在不同方向上分支的阈值。为此,Material Design 响应式布局网格提供了宽度和高度的断点,可让您将原始大小映射到离散的标准化组,这些组亦称为窗口大小类别。由于垂直滚动的普遍存在,大多数应用主要关注宽度大小类别,因此大多数应用可以通过仅处理几个断点来针对所有屏幕尺寸进行优化。(如需详细了解窗口大小类别,请参阅支持不同的屏幕尺寸。)

永久性界面元素

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

永久性元素可以是响应式的,通常占据窗口的全宽或全高,因此最好使用大小类别来决定要将它们放置在何处。这样就能划定留给内容的空间。在以下代码段中,对于较小的屏幕,activity 使用底部栏;对于较大的屏幕,activity 使用顶部应用栏。符合条件的布局使用的是如前所述的宽度断点。

<!-- 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>

内容

定位完永久性界面元素后,使用剩余的空间来显示内容,例如通过将 NavHostFragment 与应用的导航图一起使用来完成此操作。有关其他注意事项,请参阅响应式界面的导航

确保在不同大小下所有数据都可以呈现

如今,大多数应用框架所使用的数据模型,与促成界面的 Android 组件(activity、fragment 和 View)是分开的。对于 Jetpack 来说,此角色通常由 ViewModel 来充当,ViewModel 还有一个好处,那就是在配置更改后保持不变(如需了解详情,请参阅 ViewModel 概览)。

在实现适应不同大小的布局时,您可能很想根据当前大小来使用不同的数据模型。然而,这违背了单向数据流的原则。数据应向下流动到视图,而诸如用户互动之类的事件则应向上流动。如果在另一个方向上创建一种依赖关系,其中数据模型依赖于界面层的配置,则会使情况变得非常复杂。当应用改变大小时,您必须考虑从一个数据模型转换为另一个数据模型。

您应让数据模型容纳最大的大小类别,然后便可以选择性地显示、隐藏或重新定位界面中的内容,以适应当前的大小类别。如果您要决定在大小类别之间转换时布局应表现出怎样的行为,可以采用下面这些策略。

扩展内容

规范布局:Feed

您可以使用扩展的空间来扩展内容,并重新设置内容的格式,使其更易于访问。

扩大集合。许多应用会在滚动容器(例如 RecyclerViewScrollView)中显示项目的集合。使容器能够自动扩大意味着可以显示更多内容。不过,应格外小心,容器中的内容不要过度拉伸或扭曲。例如,对于 RecyclerView 来说,当宽度不紧凑时,不妨考虑使用不同的布局管理器,如 GridLayoutManagerStaggeredGridLayoutManagerFlexboxLayout

设备折叠和展开状态的对比,显示了不同的布局管理器如何根据宽度大小类别以不同的方式来设置应用的布局。
图 2. 针对不同窗口大小类的不同布局管理器。

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

强调主打元素。如果布局具有特定的焦点(如图片或视频),请在应用窗口增大时扩展该焦点,以保持用户的注意力。其他辅助元素可以重新排列在主打视图周围或下面。

构造此类布局的方法有很多,但 ConstraintLayout 特别适合此目的,因为它提供了多种方法来约束子视图的大小(包括按百分比约束或强制采用某个宽高比),以及相对于它自身或其他子级来定位其子级。您可以在使用 ConstraintLayout 构建响应式界面中详细了解所有这些功能。

默认情况下显示可收起的内容。当有可用的空间时,应呈现本来只有通过额外的用户互动(如点按、滚动或手势)才能访问的内容。例如,某些内容在空间较小时出现在标签页式界面中,当有更多可用空间时,可以将这些内容重新排列在列或列表中。

扩大外边距。如果空间太大,即使在利用了所有内容之后,您也无法找到吸引人的合适位置,那么请扩大布局的外边距,以使内容保持居中,并且各个视图具有自然的大小和间距。

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

标准手机和展开的可折叠手机,前者显示了一个全屏对话框,后者将同一对话框显示为浮动窗口。
图 3. 在中等宽度和展开宽度下,全屏对话框转换为标准对话框。

添加内容

规范化布局:辅助窗格、列表详情视图

使用辅助窗格。辅助窗格可以呈现与主要内容相关的额外内容或关联操作,例如文档中的注释或播放列表中的项目。通常,它们会在展开高度下使用屏幕底部的三分之一,或在展开宽度下使用屏幕尾部的三分之一。

一个重要的考虑因素是,当没有足够的空间来显示窗格时要将此内容放置在何处。下面是您可以探索的一些备选方案:

  • 尾部边缘的侧面抽屉式导航栏(使用 DrawerLayout
  • 底部抽屉式导航栏(使用 BottomSheetBehavior
  • 可通过点按菜单图标访问的菜单或弹出式窗口
图 4. 在辅助窗格中呈现额外内容的备选方式。

创建双窗格布局。大屏幕可能会显示功能的组合,这些功能通常单独出现在较小的屏幕上。许多应用中的常见互动模式是显示项目的列表(如联系人或搜索结果),并在用户选择了某个项目时切换到该项目的详情。应使用列表详情视图在双窗格布局中并排显示这两项功能,而不是针对较大的屏幕放大列表。与辅助窗格不同,列表详情视图的详情窗格是一个独立的元素,该元素可以单独显示在较小的屏幕上。

使用 SlidingPaneLayout 专用 widget 实现列表详情视图。此 widget 可以根据为两个窗格指定的 layout_width 值自动计算是否有足够的空间来一起显示这两个窗格,任何剩余的空间均可使用 layout_weight 进行分配。如果没有足够的空间,则每个窗格都会使用布局的全宽,详情窗格要么滑出屏幕,要么在列表窗格之上。

在配备宽显示屏的设备上,SlidingPaneLayout 显示了列表详情布局的两个窗格。
图 5. SlidingPaneLayout 在较大的宽度下显示两个窗格,在较小的宽度下显示一个窗格。

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

其他资源