在手机上限制应用方向,但在大屏幕设备上不限制

您的应用在手机的纵向模式下运行良好,因此您已将应用限制为仅纵向。但您看到有机会在大屏幕设备的横向模式下实现更多功能。

如何做到两全其美——在小屏幕上将应用限制为纵向,但在大屏幕上启用横向?

本指南是一项临时措施,直到您可以改进您的应用以完全支持所有设备配置为止。

管理应用方向

若要在大屏幕上启用横向模式,请将您的应用清单设置为默认处理方向更改。在运行时,确定应用窗口大小。如果应用窗口较小,则通过替换清单方向设置来限制应用的方向。

1. 在应用清单中指定方向设置

您可以避免声明应用清单的 screenOrientation 元素(在这种情况下,方向默认为 unspecified),也可以将屏幕方向设置为 fullUser。如果用户未锁定基于传感器的旋转,您的应用将支持所有设备方向。

<activity
    android:name=".MyActivity"
    android:screenOrientation="fullUser">

unspecifiedfullUser 之间的区别很细微但很重要。如果您不声明 screenOrientation 值,系统会选择方向,并且系统用于定义方向的策略可能因设备而异。另一方面,指定 fullUser 更符合用户为设备定义的行为:如果用户锁定基于传感器的旋转,应用将遵循用户偏好设置;否则,系统允许四种可能的屏幕方向中的任意一种(纵向、横向、反向纵向或反向横向)。请参阅 screenOrientation

2. 确定屏幕尺寸

清单设置支持所有用户允许的方向后,您可以根据屏幕尺寸通过编程方式指定应用方向。

Jetpack WindowManager 库添加到模块的 build.gradlebuild.gradle.kts 文件中

Kotlin

implementation("androidx.window:window:version")
implementation("androidx.window:window-core:version")

Groovy

implementation 'androidx.window:window:version'
implementation 'androidx.window:window-core:version'

使用 Jetpack WindowManager WindowMetricsCalculator#computeMaximumWindowMetrics() 方法将设备屏幕尺寸作为 WindowMetrics 对象获取。可以将窗口指标与窗口尺寸类进行比较,以确定何时限制方向。

窗口尺寸类提供了小屏幕和大屏幕之间的断点。

使用 WindowWidthSizeClass#COMPACTWindowHeightSizeClass#COMPACT 断点来确定屏幕尺寸

Kotlin

/** Determines whether the device has a compact screen. **/
fun compactScreen() : Boolean {
    val metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this)
    val width = metrics.bounds.width()
    val height = metrics.bounds.height()
    val density = resources.displayMetrics.density
    val windowSizeClass = WindowSizeClass.compute(width/density, height/density)

    return windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT ||
        windowSizeClass.windowHeightSizeClass == WindowHeightSizeClass.COMPACT
}

Java

/** Determines whether the device has a compact screen. **/
private boolean compactScreen() {
    WindowMetrics metrics = WindowMetricsCalculator.getOrCreate().computeMaximumWindowMetrics(this);
    int width = metrics.getBounds().width();
    int height = metrics.getBounds().height();
    float density = getResources().getDisplayMetrics().density;
    WindowSizeClass windowSizeClass = WindowSizeClass.compute(width/density, height/density);
    return windowSizeClass.getWindowWidthSizeClass() == WindowWidthSizeClass.COMPACT ||
                windowSizeClass.getWindowHeightSizeClass() == WindowHeightSizeClass.COMPACT;
}
    注意
  • 示例是作为 activity 的方法实现的;因此,activity 在 computeMaximumWindowMetrics() 的参数中被解引用为 this
  • 之所以使用 computeMaximumWindowMetrics() 方法而不是 computeCurrentWindowMetrics(),是因为应用可以在多窗口模式下启动,在这种模式下会忽略屏幕方向设置。只有当应用窗口是整个设备屏幕时,确定应用窗口大小并替换方向设置才有意义。

有关如何声明依赖项以在您的应用中提供 computeMaximumWindowMetrics() 方法的说明,请参阅 WindowManager

3. 替换应用清单设置

确定设备屏幕尺寸紧凑后,您可以调用 Activity#setRequestedOrientation() 来替换清单的 screenOrientation 设置

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    requestedOrientation = if (compactScreen())
        ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
        ActivityInfo.SCREEN_ORIENTATION_FULL_USER
    ...
    // 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)
            requestedOrientation = if (compactScreen())
                ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else
                ActivityInfo.SCREEN_ORIENTATION_FULL_USER
        }
    })
}

Java

@Override
protected void onCreate(Bundle savedInstance) {
    super.onCreate(savedInstanceState);
    if (compactScreen()) {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    } else {
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
    }
    ...
    // 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);
            if (compactScreen()) {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
            } else {
                setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER);
            }
        }
    });
}

通过将逻辑添加到 onCreate()View.onConfigurationChanged() 方法中,您能够在 activity 调整大小或在显示屏之间移动时(例如设备旋转后或折叠设备折叠或展开时)获取最大窗口指标并替换方向设置。如需详细了解配置更改何时发生以及何时导致 activity 重建,请参阅处理配置更改

要点

结果

现在,您的应用应该在小屏幕上保持纵向,无论设备如何旋转。在大屏幕上,应用应该支持横向和纵向。

包含本指南的集合

本指南是这些精选的快速指南集合的一部分,这些集合涵盖了更广泛的 Android 开发目标

使您的应用能够支持在平板电脑、可折叠设备和 ChromeOS 设备上的优化用户体验。

有疑问或反馈?

请访问我们的常见问题页面了解快速指南,或联系我们告知您的想法。