窗口尺寸类别

窗口尺寸类别是一组有观点的视口断点,可帮助您设计、开发和测试响应式/自适应布局。这些断点在布局简单性和针对独特用例优化应用的灵活性之间取得平衡。

窗口尺寸类别将应用可用的显示区域分类为紧凑型中型扩展型。可用宽度和高度分别进行分类,因此在任何时间点,您的应用都有两个窗口尺寸类别——一个用于宽度,一个用于高度。由于垂直滚动的普遍存在,可用宽度通常比可用高度更重要,因此宽度窗口尺寸类别可能与您的应用 UI 更相关。

图 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类型的逻辑。相反,窗口尺寸类别由应用可用的窗口大小决定,而不管应用运行在何种类型的设备上,这有两个重要的含义

  • 物理设备不能保证特定的窗口尺寸类别。应用可用的屏幕空间可能因多种原因而与设备的屏幕尺寸不同。在移动设备上,分屏模式可以在两个应用之间划分屏幕。在 ChromeOS 上,Android 应用可以显示在任意可调整大小的自由形式窗口中。折叠屏设备可以有两个不同尺寸的屏幕,通过折叠或展开设备分别访问。

  • 窗口尺寸类别可以在应用的生命周期内发生变化。在应用运行期间,设备方向变化、多任务处理以及折叠/展开操作都会改变可用的屏幕空间。因此,窗口尺寸类别是动态的,并且应用的 UI 应该相应地进行调整。

窗口尺寸类别映射到Material Design 布局指南中的紧凑型、中型和扩展型断点。使用窗口尺寸类别做出高级应用布局决策,例如决定是否使用特定的规范布局来利用额外的屏幕空间。

您可以使用Jetpack WindowManager库提供的WindowSizeClassWindowSizeClass#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.
    }
}

测试窗口尺寸类别

在进行布局更改时,请测试所有窗口大小下的布局行为,尤其是在紧凑型、中型和扩展型断点宽度处。

如果您有现有的紧凑型屏幕布局,请首先针对扩展宽度尺寸类别优化您的布局,因为此尺寸类别为其他内容和 UI 更改提供了更多空间。然后决定哪种布局适合中等宽度尺寸类别;考虑添加专门的布局。

后续步骤

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

要详细了解哪些因素使应用在所有设备和屏幕尺寸上都表现出色,请参阅