使用窗口大小类

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

窗口尺寸类别将应用可用的显示区域分类为紧凑型中等型扩展型。可用宽度和高度分别进行分类,因此在任何时间点,您的应用都有两个窗口尺寸类别——一个用于宽度,一个用于高度。由于垂直滚动的普遍存在,可用宽度通常比可用高度更重要,因此宽度窗口尺寸类别可能与您的应用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更改提供了更多空间。然后决定哪种布局适合中等宽度尺寸类别;考虑添加专用布局。

后续步骤

要了解有关如何使用窗口尺寸类别创建响应式/自适应布局的更多信息,请参阅以下内容:

要了解有关使应用在所有设备和屏幕尺寸上都表现出色的更多信息,请参阅: