窗口大小类别

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

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

图 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 应用可呈现在可任意调整大小的自由式窗口中。可折叠设备可以有两个大小不同的屏幕,分别可通过折叠或展开设备访问。

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

窗口大小类别对应于 Material Design 布局指南中的紧凑断点、中等断点和较大断点。 使用窗口大小类别做出粗略的应用布局决策,例如决定是否使用特定的规范布局以利用额外的屏幕空间。

You can compute the current WindowSizeClass using the WindowSizeClass#compute() function provided by the Jetpack WindowManager library. The following example shows how to calculate the window size class and receive updates whenever the window size class changes:

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.
    }
}

测试窗口大小类别

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

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

后续步骤

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

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