支持不同的屏幕尺寸

如果您的应用支持不同的屏幕尺寸,则可让各种各样的设备和最大量的用户使用您的应用。

为了尽量支持各种屏幕尺寸,应用应该采用自适应布局。响应式/自适应布局无论屏幕尺寸如何,都能提供优化的用户体验,让您的应用能够适应手机、平板电脑、可折叠设备、ChromeOS 设备、纵向和横向屏幕方向以及可调整大小的配置(例如多窗口模式)。

窗口大小类别

窗口大小类别是一组主观的视口断点,有助于您设计、开发和测试响应式/自适应布局。这些断点平衡了布局简单性与针对独特情形优化应用的灵活性。

窗口大小类别将应用中可用的显示区域分类为:较小中等展开。可用宽度和高度是单独分类的,因此在任何时间点,应用都有两个窗口大小类别:宽度窗口大小类别和高度窗口大小类别。由于垂直滚动的普遍存在,可用宽度通常比可用高度更重要,因此宽度窗口大小类别可能与应用的界面更相关。

图 1. 基于宽度的窗口大小类的表示。
图 2. 基于高度的窗口大小类别图示。

如图所示,这些断点可让您继续从设备和配置的角度考虑布局。每个大小类别划分点代表了典型设备场景的大多数情况,当您考虑基于划分点的布局设计时,这可能是一个有用的参考框架。

大小类别 划分点 设备表示
较小的宽度 宽度 < 600dp 99.96% 的手机处于竖屏模式
中等宽度 600dp ≤ 宽度 < 840dp 93.73% 的平板电脑处于竖屏模式,

最大展开状态的内屏(竖屏模式)

较大宽度 宽度 ≥ 840dp 97.22% 的平板电脑处于横屏模式,

展开状态下的最大内屏(横向显示)

较小的高度 高度 < 480dp 99.78% 的手机处于横屏模式
中等高度 480dp ≤ 高度 < 900dp 96.56% 的平板电脑处于横屏模式,

97.59% 的手机处于竖屏模式

展开高度 高度 ≥ 900dp 94.25% 的平板电脑处于竖屏模式

虽然将大小类别可视化为实体设备可能很有用,但窗口大小类别不是由设备屏幕尺寸明确决定的。窗口大小类别不适用于 isTablet‐type 逻辑,相反,窗口大小类别取决于应用可用的窗口大小,而无论在哪种类型的设备上运行应用,这有两个重要影响:

  • 实体设备不能保证特定的窗口大小类别。应用可用的屏幕空间可能会与设备的屏幕尺寸不同,这有很多原因。在移动设备上,分屏模式可以在两个应用之间分割屏幕。在 ChromeOS 中,Android 应用可以呈现在可任意调整大小的自由式窗口中。可折叠设备可以有两个大小不同的屏幕,分别可通过折叠或展开设备使用。

  • 窗口大小类别在应用的整个生命周期内可能会发生变化。当应用处于运行状态时,设备更改屏幕方向、进行多任务处理和折叠/展开可能会改变可用的屏幕空间量。因此,窗口大小类别是动态的,应用的界面应相应地调整。

窗口大小类别与 Material Design 自适应布局网格中的布局划分点一一对应。使用窗口大小类别做出高级应用布局决策,例如决定是否使用特定的规范布局以利用额外的屏幕空间。

您可以使用 Jetpack WindowManager 库提供的 WindowSizeClass#compute() 函数来计算当前的 WindowSizeClass。以下示例展示了如何计算窗口大小类别,并在窗口大小类别发生变化时接收更新:

Kotlin

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // ...

        // Replace with a known container that you can safely add a
        // view to where the view won't affect the layout and the view
        // won't be replaced.
        val container: ViewGroup = binding.container

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged(). This is required for all
        // activities, even those that don't handle configuration
        // changes. You can't use Activity.onConfigurationChanged(),
        // since there are situations where that won't be called when
        // the configuration changes. View.onConfigurationChanged() is
        // called in those scenarios.
        container.addView(object : View(this) {
            override fun onConfigurationChanged(newConfig: Configuration?) {
                super.onConfigurationChanged(newConfig)
                computeWindowSizeClasses()
            }
        })

        computeWindowSizeClasses()
    }

    private fun computeWindowSizeClasses() {
        val metrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(this)
        val width = metrics.bounds.width()
        val height = metrics.bounds.height()
        val density = resources.displayMetrics.density
        val windowSizeClass = WindowSizeClass.compute(width/density, height/density)
        // COMPACT, MEDIUM, or EXPANDED
        val widthWindowSizeClass = windowSizeClass.windowWidthSizeClass
        // COMPACT, MEDIUM, or EXPANDED
        val heightWindowSizeClass = windowSizeClass.windowHeightSizeClass

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

Java

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // ...

        // Replace with a known container that you can safely add a
        // view to where the view won't affect the layout and the view
        // won't be replaced.
        ViewGroup container = binding.container;

        // Add a utility view to the container to hook into
        // View.onConfigurationChanged(). This is required for all
        // activities, even those that don't handle configuration
        // changes. You can't use Activity.onConfigurationChanged(),
        // since there are situations where that won't be called when
        // the configuration changes. View.onConfigurationChanged() is
        // called in those scenarios.
        container.addView(new View(this) {
            @Override
            protected void onConfigurationChanged(Configuration newConfig) {
                super.onConfigurationChanged(newConfig);
                computeWindowSizeClasses();
            }
        });

        computeWindowSizeClasses();
    }

    private void computeWindowSizeClasses() {
        WindowMetrics metrics = WindowMetricsCalculator.getOrCreate()
                .computeCurrentWindowMetrics(this);

        int width = metrics.getBounds().width
        int height = metrics.getBounds().height()
        float density = getResources().getDisplayMetrics().density;
        WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density)
        // COMPACT, MEDIUM, or EXPANDED
        WindowWidthSizeClass widthWindowSizeClass = windowSizeClass.getWindowWidthSizeClass()
        // COMPACT, MEDIUM, or EXPANDED
        WindowHeightSizeClass heightWindowSizeClass = windowSizeClass.getWindowHeightSizeClass()

        // Use widthWindowSizeClass and heightWindowSizeClass.
    }
}

在应用中观察窗口大小类别之后,您就可以开始根据当前的窗口大小类别来改变布局了。

布局和窗口大小类

在更改布局时,请在所有窗口大小下测试布局行为,尤其是在较小、中等和较大断点宽度下。

如果您的现有布局是针对较小屏幕的,请先针对较大宽度大小类别优化布局,因为此大小类别为额外的内容和界面更改提供了最大的空间。然后,确定哪种布局对中等宽度大小类别有意义;考虑添加专用布局。

后续步骤

如需详细了解如何使用窗口大小类别创建响应式/自适应布局,请参阅以下内容:

如需详细了解如何让应用在所有设备上以及所有屏幕尺寸下都表现出色,请参阅: