Cómo restringir la orientación de las apps en teléfonos, pero no en dispositivos con pantalla grande

Tu app funciona muy bien en teléfonos con orientación vertical, por lo que la restringiste a ese modo. Sin embargo, consideras que puedes aprovechar más las pantallas grandes en orientación horizontal.

¿Cómo puedes hacer las dos cosas: restringir la app a la orientación vertical en pantallas pequeñas y habilitar la orientación horizontal en pantallas grandes?

Esta guía es una medida temporal hasta que puedas mejorar tu app para proporcionar compatibilidad total con todas las configuraciones de los dispositivos.

Cómo administrar la orientación de la app

Para habilitar la orientación horizontal en pantallas grandes, configura el manifiesto de la app para que controle los cambios de orientación de forma predeterminada. Durante el tiempo de ejecución, determina el tamaño de la ventana de la app. Si la ventana de la app es pequeña, anula la configuración de la orientación del manifiesto para restringir la orientación de la app.

1. Especifica la configuración de orientación en el manifiesto de la app

Puedes evitar declarar el elemento screenOrientation del manifiesto de la app (en cuyo caso, la orientación se establece de forma predeterminada como unspecified) o establecer la orientación de la pantalla como fullUser. Si el usuario no bloqueó la rotación basada en sensores, tu app admitirá todas las orientaciones del dispositivo.

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

La diferencia entre unspecified y fullUser es sutil, pero importante. Si no declaras un valor de screenOrientation, el sistema elige la orientación, y la política que usa para definirla puede diferir de un dispositivo a otro. Por otro lado, especificar fullUser coincide más con el comportamiento que el usuario definió para el dispositivo: si el usuario bloqueó la rotación basada en el sensor, la app sigue la preferencia del usuario; de lo contrario, el sistema permite cualquiera de las cuatro orientaciones de pantalla posibles (vertical, horizontal, vertical inversa o horizontal inversa). Consulta screenOrientation.

2. Determina el tamaño de la pantalla

Con el manifiesto configurado para admitir todas las orientaciones permitidas por el usuario, puedes especificar la orientación de la app de manera programática según el tamaño de la pantalla.

Agrega las bibliotecas de Jetpack WindowManager al archivo build.gradle o build.gradle.kts del módulo:

Kotlin

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

Groovy

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

Usa el método WindowMetricsCalculator#computeMaximumWindowMetrics() de Jetpack WindowManager para obtener el tamaño de pantalla del dispositivo como un objeto WindowMetrics. Las métricas de ventana se pueden comparar con las clases de tamaño de ventana para decidir cuándo restringir la orientación.

Las clases de tamaño de ventanas proporcionan los puntos de interrupción entre las pantallas pequeñas y las grandes.

Usa los puntos de interrupción WindowWidthSizeClass#COMPACT y WindowHeightSizeClass#COMPACT para determinar el tamaño de la pantalla:

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;
}
    Nota:
  • Los ejemplos se implementan como métodos de una actividad. Por lo tanto, se hace referencia a la actividad como this en el argumento de computeMaximumWindowMetrics().
  • Se usa el método computeMaximumWindowMetrics() en lugar de computeCurrentWindowMetrics(), ya que la app se puede iniciar en el modo multiventana, que ignora la configuración de orientación de la pantalla. No tiene sentido determinar el tamaño de la ventana de la app y anular la configuración de orientación, a menos que la ventana de la app sea la de todo el dispositivo.

Consulta WindowManager para obtener instrucciones sobre cómo declarar dependencias y que el método computeMaximumWindowMetrics() esté disponible en tu app.

3. Anula la configuración del manifiesto de la app

Cuando hayas determinado que el dispositivo tiene un tamaño de pantalla compacto, puedes llamar a Activity#setRequestedOrientation() para anular el parámetro screenOrientation del manifiesto:

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);
            }
        }
    });
}

Si agregas la lógica a los métodos onCreate() y View.onConfigurationChanged(), puedes obtener las métricas máximas de la ventana y anular la configuración de orientación cada vez que se cambie el tamaño de la actividad o se la mueva entre pantallas, como después de la rotación del dispositivo o cuando un dispositivo plegable se pliega o se despliega. Para obtener más información sobre cuándo se producen los cambios de configuración y cuándo provocan recreación de actividad, consulta Cómo administrar los cambios en la configuración.

Puntos clave

  • screenOrientation: Es el parámetro de configuración del manifiesto de la app que te permite especificar cómo responde la app a los cambios de orientación del dispositivo.
  • Jetpack WindowManager: Es un conjunto de bibliotecas que te permiten determinar el tamaño y la relación de aspecto de la ventana de la app; es retrocompatible hasta el nivel de API 14.
  • Activity#setRequestedOrientation(): Es el método con el que puedes cambiar la orientación de la app en el tiempo de ejecución.

Resultados

Ahora la app debería permanecer en orientación vertical en pantallas pequeñas, independientemente de la rotación del dispositivo. En pantallas grandes, la app debería admitir la orientación horizontal y vertical.

Colecciones que contienen esta guía

Esta guía forma parte de estas colecciones de guías rápidas seleccionadas que abarcan objetivos más amplios de desarrollo de Android:

Habilita tu app para que admita una experiencia del usuario optimizada en tablets, dispositivos plegables y ChromeOS.

Tienes preguntas o comentarios

Ve a nuestra página de preguntas frecuentes para obtener información sobre las guías rápidas o comunícate con nosotros para contarnos tu opinión.