将界面迁移到响应式布局

随着设备类型不断增多,Android 应用需要为不同类型的设备提供支持。也就是说,应用界面应该是响应式的,能够适应各种屏幕尺寸以及不同的屏幕方向和设备状态。

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

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

连续性是指在切换窗口大小时,提供顺畅的用户体验。无论用户置身于何种体验,相应体验都应能连续进行而不中断。由于视图大小在发生变化时,可能导致整个视图层次结构被销毁然后重新建立,因此请务必确保用户不会丢失其当前所在的视图位置或数据。

需要避免的事项

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

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

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

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

此外,还应避免尝试认定设备属于手机还是平板电脑。平板电脑的具体定义方式并无客观标准:是必须具备一定的大小还是宽高比,亦或是二者兼备呢?随着设备规格不断推陈出新,这些假设可能会发生变化,相应的区别也就不那么重要了。

因此,在设计应用布局时请使用断点和窗口大小类,而非上述策略。

断点和窗口大小类

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

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

永久性界面元素

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

永久性元素可以是响应式的,通常占据窗口的全宽或全高,因此最好使用大小类来决定要将它们放置在何处。这样就能划定留给内容的空间。在以下代码段中,对于较小的屏幕,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 的更多详情。另请注意,此模式可能会影响您构造导航图的方式(请参阅响应式界面的导航)。

其他资源