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

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

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

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

连续性是指在从一个窗口大小过渡到另一个窗口大小时,提供无缝的用户体验。无论用户参与的体验是什么,都应该在没有中断的情况下继续。由于尺寸变化可能伴随着整个视图层次结构的销毁和重新创建,因此重要的是用户不会失去其位置或数据。

需要避免的事项

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

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

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 组件(活动、片段和视图)分离的数据模型。使用 Jetpack 时,此角色通常由 ViewModel 履行,ViewModel 具有在配置更改中生存的额外优势(有关更多信息,请参阅ViewModel 概述)。

在实现适应不同尺寸的布局时,可能会倾向于根据当前尺寸使用不同的数据模型。但是,这违背了单向数据流的原则。数据应该向下流动到视图,而用户交互等事件应该向上流动。在数据模型依赖于 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 的导航)。

其他资源