Guía de soluciones para pantallas grandes

Android proporciona todos los ingredientes para desarrollar apps destinadas a pantallas grandes de cinco estrellas. En las recetas de esta guia, se seleccionan y combinan ingredientes de opciones para resolver problemas de desarrollo específicos. Cada receta incluye prácticas recomendadas, muestras de código de calidad y, además, instrucciones paso a paso para ayudarte a convertirte en un gran chef de las pantallas grandes.

Calificaciones por estrellas

Las recetas se califican por estrellas según la medida en que se ajustan a los lineamientos de calidad de apps para pantallas grandes.

Calificación de cinco estrellas Cumple con los criterios del Nivel 1: diferenciada para pantalla grande
Calificación de cuatro estrellas Cumple con los criterios del Nivel 2: optimizada para pantalla grande
Calificación de tres estrellas Cumple con los criterios del Nivel 3: preparada para pantalla grande
Calificación de dos estrellas Proporciona algunas capacidades para pantallas grandes, pero no cumple con los lineamientos de calidad correspondientes
Calificación de una estrella Satisface las necesidades de un caso de uso específico, pero no es compatible con pantallas grandes

Compatibilidad con la cámara de Chromebook

Calificación de tres estrellas

Haz que los usuarios de Chromebook te vean en Google Play.

Si tu app de cámara solo admite las funciones básicas de cámara, no permitas que las tiendas de aplicaciones eviten que los usuarios de Chromebook instalen la app solo porque especificaste involuntariamente funciones de cámara avanzadas de teléfonos de alta gama.

Las Chromebooks tienen una cámara frontal (orientadas al usuario) integrada que funciona bien para videoconferencias, instantáneas y otras aplicaciones. Sin embargo, no todas las Chromebooks tienen una cámara posterior (orientadas al mundo), y la mayoría de las cámaras orientadas al usuario no admiten el enfoque automático ni el flash.

Prácticas recomendadas

Las apps de cámara versátiles admiten todos los dispositivos, independientemente de la configuración de la cámara: dispositivos con cámaras frontales, cámaras posteriores y cámaras externas conectadas por USB.

Para garantizar que las tiendas de aplicaciones permitan que la app esté disponible para la mayor cantidad de dispositivos posible, declara siempre todas las funciones de cámara que esta use e indica de forma explícita si las funciones son obligatorias o no.

Ingredientes

  • Permiso de CAMERA: Otorga a tu app acceso a las cámaras de un dispositivo.
  • Elemento de manifiesto <uses-feature>: Informa a las tiendas de aplicaciones sobre las funciones que usa la app.
  • Atributo required: Indica a las tiendas de aplicaciones si tu app puede funcionar sin una función especificada.

Pasos

Resumen

Declara el permiso CAMERA. Declara las funciones de cámara que proporcionan compatibilidad básica con la cámara. Especifica si cada función es obligatoria o no.

1. Declara el permiso CAMERA

Agrega el siguiente permiso al manifiesto de la app:

<uses-permission android:name="android.permission.CAMERA" />
2. Declara las funciones básicas de la cámara

Agrega las siguientes funciones al manifiesto de la app:

<uses-feature android:name="android.hardware.camera.any" android:required="false" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
<uses-feature android:name="android.hardware.camera.flash" android:required="false" />
3. Especifica si cada función es obligatoria

Configura android:required="false" para la función android.hardware.camera.any para habilitar el acceso a tu app en dispositivos que tengan cualquier tipo de cámara integrada o externa, o que no tengan ninguna cámara.

En el caso de las otras funciones, configura android:required="false" para garantizar que los dispositivos como las Chromebooks que no tienen cámara posterior, enfoque automático o flash puedan acceder a tu app en tiendas de aplicaciones.

Resultados

Los usuarios de Chromebook pueden descargar e instalar tu app desde Google Play y otras tiendas de aplicaciones. Además, los dispositivos que admiten todas las funciones de la cámara, como los teléfonos, no tendrán restricciones de funcionalidad de cámara.

Si configuras de forma explícita las funciones de cámara compatibles con tu app y especificas las funciones que esta requiere, la app estará disponible para la mayor cantidad posible de dispositivos.

Recursos adicionales

Para obtener más información, consulta Funciones de hardware de la cámara en la documentación de <uses-feature>.

Orientación de la app restringida en los teléfonos, pero no en los dispositivos con pantalla grande

Calificación de dos estrellas

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?

Prácticas recomendadas

Las mejores apps respetan las preferencias de los usuarios, como la orientación del dispositivo.

Los lineamientos de calidad de las apps para pantalla grande recomiendan que estas admitan todas las configuraciones de dispositivos, incluidas las orientaciones vertical y horizontal, el modo multiventana y los estados plegado y desplegado de los dispositivos plegables. Las apps deben optimizar los diseños y las interfaces de usuario para diferentes configuraciones y deben guardar y restablecer el estado durante los cambios de configuración.

Esta receta es una medida temporal: una pizca de compatibilidad con pantallas grandes. Úsala hasta que puedas mejorar tu app para proporcionar compatibilidad total con todas las configuraciones de los dispositivos.

Ingredientes

  • screenOrientation: 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: Conjunto de bibliotecas que te permiten determinar el tamaño y la relación de aspecto de la ventana de la app; retrocompatible hasta el nivel de API 14
  • Activity#setRequestedOrientation(): Método con el que puedes cambiar la orientación de la app en el tiempo de ejecución

Pasos

Resumen

Habilita la app para controlar los cambios de orientación de forma predeterminada en el manifiesto de la app. 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, la 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 con el comportamiento que el usuario definió para el dispositivo: si el usuario bloqueó la rotación basada en sensores, la app sigue la preferencia del usuario. De lo contrario, el sistema admite cualquiera de las cuatro orientaciones posibles de la pantalla (vertical, horizontal, vertical inverso u horizontal inverso). Consulta android: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 WindowManager de Jetpack 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 WindowManager de Jetpack WindowMetricsCalculator#computeMaximumWindowMetrics() 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 anteriores 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 para 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 compacta, 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.

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.

Recursos adicionales

Si necesitas ayuda con la actualización de la app para admitir todas las configuraciones de dispositivos en todo momento, consulta lo siguiente:

Cómo pausar y reanudar la reproducción de contenido multimedia con la barra espaciadora del teclado externo

Calificación de cuatro estrellas

La optimización para pantallas grandes incluye la posibilidad de controlar entradas de teclado externas, como reaccionar cuando se presiona la barra espaciadora para pausar o reanudar la reproducción de videos y otro contenido multimedia. Esto es particularmente útil para tablets, que a menudo se conectan a teclados externos, y Chromebooks, que suelen tener teclados externos, pero que se pueden usar en modo tablet.

Cuando el contenido multimedia sea el único elemento de la ventana (como la reproducción de video en pantalla completa), puede responder a eventos de pulsación de teclas a nivel de actividad o, en Jetpack Compose, a nivel de pantalla.

Prácticas recomendadas

Cada vez que la app reproduce un archivo multimedia, los usuarios deben poder pausar y reanudar la reproducción presionando la barra espaciadora en un teclado físico.

Ingredientes

  • KEYCODE_SPACE: Clave de código constante para la barra espaciadora.

Compose

  • onPreviewKeyEvent: Modifier permite que un componente intercepte eventos clave de hardware cuando este (o uno de sus elementos secundarios) esté enfocado.
  • onKeyEvent: Al igual que onPreviewKeyEvent, este Modifier permite que un componente intercepte eventos clave de hardware cuando este (o uno de sus elementos secundarios) esté enfocado.

Views

  • onKeyUp(): Se llama cuando se libera una clave y no la maneja una vista dentro de una actividad.

Pasos

Resumen

Las apps basadas en objetos View y las basadas en Jetpack Compose responden a las pulsaciones de teclas del teclado de forma similar: la app debe escuchar los eventos de pulsación de teclas, filtrarlos y responder a las pulsaciones de teclas seleccionadas, como cuando se presiona la barra espaciadora.

1. Cómo escuchar eventos del teclado

Views

En una actividad de tu app, anula el método onKeyUp():

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
    ...
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    ...
}

El método se invoca cada vez que se suelta una tecla presionada, por lo que se activa exactamente una vez por cada vez que se presiona una tecla.

Compose

Con Jetpack Compose, puedes aprovechar el modificador onPreviewKeyEvent o onKeyEvent dentro de la pantalla que administra la pulsación de teclas:

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

o

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp) {
        ...
    }
    ...
})

2. Cómo filtrar pulsaciones de la barra espaciadora

Dentro del método onKeyUp() o de los métodos de modificador onPreviewKeyEvent y onKeyEvent de Compose, filtra por KeyEvent.KEYCODE_SPACE para enviar el evento correcto al componente multimedia:

Views

Kotlin

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback()
    return true
}
return false

Java

if (keyCode == KeyEvent.KEYCODE_SPACE) {
    togglePlayback();
    return true;
}
return false;

Compose

Column(modifier = Modifier.onPreviewKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

o

Column(modifier = Modifier.onKeyEvent { event ->
    if (event.type == KeyEventType.KeyUp && event.key == Key.Spacebar) {
        ...
    }
    ...
})

Resultados

Tu app ahora puede responder cuando se presiona la barra espaciadora para pausar y reanudar un video o cualquier otro contenido multimedia.

Recursos adicionales

Para obtener más información sobre los eventos del teclado y cómo administrarlos, consulta Cómo controlar las entradas del teclado.

Rechazo de la palma con la pluma stylus

Calificación de cinco estrellas

Una pluma stylus puede ser una herramienta excepcionalmente productiva y creativa en pantallas grandes. Sin embargo, cuando los usuarios dibujan, escriben o interactúan con una app usando una pluma stylus, a veces tocan la pantalla con la palma de las manos. El evento táctil se puede informar a tu app antes de que el sistema reconozca el evento y lo descarte como un toque de la palma accidental.

Prácticas recomendadas

Tu app debe identificar eventos táctiles extraños e ignorarlos. Para cancelar un toque de la palma, Android envía un objeto MotionEvent. Busca ACTION_CANCEL o ACTION_POINTER_UP y FLAG_CANCELED en el objeto para determinar si se rechazó el gesto causado por el toque de la palma.

Ingredientes

  • MotionEvent: Representa eventos táctiles y de movimiento. Contiene la información necesaria para determinar si se debe ignorar un evento.
  • OnTouchListener#onTouch(): Recibe objetos MotionEvent.
  • MotionEvent#getActionMasked(): Muestra la acción asociada con un evento de movimiento.
  • ACTION_CANCEL: Es una constante MotionEvent que indica que se debe deshacer un gesto.
  • ACTION_POINTER_UP: Es una constante MotionEvent que indica que un puntero distinto al primero se fue hacia arriba (es decir, renunció al contacto con la pantalla del dispositivo).
  • FLAG_CANCELED: Es una constante MotionEvent que indica que el puntero hacia arriba causó un evento táctil no intencional. Se agregó a los eventos ACTION_POINTER_UP y ACTION_CANCEL en Android 13 (nivel de API 33) y versiones posteriores.

Pasos

Resumen

Examina los objetos MotionEvent que se enviaron a tu app. Usa las APIs de MotionEvent para determinar las características del evento:

  • Eventos de un solo puntero: Comprueba si se cumple el evento ACTION_CANCEL. En Android 13 y versiones posteriores, también comprueba si se cumple el evento FLAG_CANCELED.
  • Eventos de varios punteros: En Android 13 y versiones posteriores, comprueba si se cumplen los eventos ACTION_POINTER_UP y FLAG_CANCELED.

Responde a los eventos ACTION_CANCEL y ACTION_POINTER_UP/FLAG_CANCELED.

1. Adquiere objetos de eventos de movimiento

Agrega un objeto OnTouchListener a tu app:

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        // Process motion event.
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    // Process motion event.
});
2. Determina la acción y las marcas de los eventos

Busca ACTION_CANCEL, que indica un evento de un solo puntero en todos los niveles de API. En Android 13 y versiones posteriores, busca el evento FLAG_CANCELED. en ACTION_POINTER_UP

Kotlin

val myView = findViewById<View>(R.id.myView).apply {
    setOnTouchListener { view, event ->
        when (event.actionMasked) {
            MotionEvent.ACTION_CANCEL -> {
                //Process canceled single-pointer motion event for all SDK versions.
            }
            MotionEvent.ACTION_POINTER_UP -> {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
                   (event.flags and MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                    //Process canceled multi-pointer motion event for Android 13 and higher.
                }
            }
        }
        true
    }
}

Java

View myView = findViewById(R.id.myView);
myView.setOnTouchListener( (view, event) -> {
    switch (event.getActionMasked()) {
        case MotionEvent.ACTION_CANCEL:
            // Process canceled single-pointer motion event for all SDK versions.
        case MotionEvent.ACTION_UP:
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
               (event.getFlags() & MotionEvent.FLAG_CANCELED) == MotionEvent.FLAG_CANCELED) {
                //Process canceled multi-pointer motion event for Android 13 and higher.
            }
    }
    return true;
});
3. Deshace el gesto

Una vez que hayas identificado el toque de la palma, puedes deshacer los efectos en pantalla del gesto.

Tu app debe mantener un historial de acciones del usuario para deshacer las entradas no deseadas, como los toques de la palma. Para ver un ejemplo, consulta Cómo implementar una app de dibujo básica en el codelab Cómo mejorar la compatibilidad con la pluma stylus en una app para Android.

Resultados

Ahora, tu app puede identificar y rechazar los toques de la palma de la mano para eventos de varios punteros en Android 13 y niveles posteriores de API, y para eventos de un solo puntero en todos los niveles de API.

Recursos adicionales

Para obtener más información, consulta lo siguiente:

Administración de estado de WebView

Calificación de tres estrellas

WebView es un componente de uso general que ofrece un sistema avanzado para la administración de estados. Una WebView debe mantener su estado y posición de desplazamiento en todos los cambios de configuración. Un objeto WebView puede perder la posición de desplazamiento cuando el usuario rota el dispositivo o despliega un teléfono plegable, lo que lo obliga a desplazarse nuevamente desde la parte superior de la WebView hasta la posición de desplazamiento anterior.

Prácticas recomendadas

Minimiza la cantidad de veces que se vuelve a crear un objeto WebView. WebView administra bien su estado y puedes aprovechar esta calidad administrando tantos cambios de configuración como sea posible. Tu app debe controlar los cambios de configuración porque la recreación de Activity (la forma en que el sistema maneja los cambios de configuración) también recrea la WebView, lo que hace que WebView pierda su estado.

Ingredientes

  • android:configChanges: Es el atributo del elemento <activity> del manifiesto. Enumera los cambios de configuración que controla la actividad.
  • View#invalidate(): Es el método que vuelve a dibujar una vista. Lo hereda WebView.

Pasos

Resumen

Para guardar el estado WebView, evita la recreación de Activity tanto como sea posible y, luego, deja que se invalide WebView para que pueda cambiar de tamaño y retener su estado.

1. Agrega cambios de configuración al archivo AndroidManifest.xml de tu app

Para evitar la recreación de actividades, especifica los cambios de configuración que maneja tu app (en lugar del sistema):

<activity
  android:name=".MyActivity"
  android:configChanges="screenLayout|orientation|screenSize
      |keyboard|keyboardHidden|smallestScreenSize" />

2. Invalida WebView cada vez que tu app recibe un cambio de configuración

Kotlin

override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    webView.invalidate()
}

Java

@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    webview.invalidate();
}

Este paso se aplica solo al sistema de vista, ya que Jetpack Compose no necesita invalidar nada para cambiar el tamaño de los elementos Composable correctamente. Sin embargo, Compose vuelve a crear una WebView con frecuencia si no se administra correctamente. Usa el wrapper Accompanist WebView para guardar y restablecer el estado WebView en tus apps de Compose.

Resultados

Los componentes WebView de tu app ahora retienen su estado y posición de desplazamiento en varios cambios de configuración, desde el cambio de tamaño hasta el cambio de orientación, el plegado y el desplegado.

Recursos adicionales

Para obtener más información sobre los cambios de configuración y cómo administrarlos, consulta Cómo administrar los cambios en la configuración.

Administración del estado de RecyclerView

Calificación de tres estrellas

RecyclerView puede mostrar grandes cantidades de datos usando recursos gráficos mínimos. A medida que una RecyclerView se desplaza por su lista de elementos, la RecyclerView reutiliza las instancias de View de elementos que se desplazaron fuera de la pantalla para crear elementos nuevos a medida que se desplazan en la pantalla. Sin embargo, los cambios de configuración, como la rotación del dispositivo, pueden restablecer el estado de una RecyclerView, lo que obliga a los usuarios a desplazarse nuevamente a su posición anterior en la lista de elementos RecyclerView.

Prácticas recomendadas

RecyclerView debe mantener su estado (en particular, la posición de desplazamiento) y el estado de los elementos de lista durante todos los cambios de configuración.

Ingredientes

Pasos

Resumen

Establece la política de restablecimiento de estado del RecyclerView.Adapter para guardar la posición de desplazamiento de RecyclerView. Guarda el estado de los elementos de lista de RecyclerView. Agrega el estado de los elementos de lista al adaptador RecyclerView y restablécelo cuando estén vinculados a un ViewHolder.

1. Habilita la política de restablecimiento de estado de Adapter

Habilita la política de restablecimiento de estado del adaptador RecyclerView para que se mantenga la posición de desplazamiento de RecyclerView en todos los cambios de configuración. Agrega la especificación de la política al constructor del adaptador:

Kotlin

class MyAdapter() : RecyclerView.Adapter() {
    init {
        stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY
    }
    ...
}

Java

class MyAdapter extends RecyclerView.Adapter {

    public Adapter() {
        setStateRestorationPolicy(StateRestorationPolicy.PREVENT_WHEN_EMPTY);
    }
    ...
}

2. Guarda el estado de los elementos de lista con estado

Guarda el estado de los elementos complejos de la lista RecyclerView, como elementos que contienen elementos EditText. Por ejemplo, para guardar el estado de un EditText, agrega una devolución de llamada similar a un controlador onClick para capturar los cambios de texto. Dentro de la devolución de llamada, define qué datos se guardarán:

Kotlin

input.addTextChangedListener(
    afterTextChanged = { text ->
        text?.let {
            // Save state here.
        }
    }
)

Java

input.addTextChangedListener(new TextWatcher() {
    
    ...

    @Override
    public void afterTextChanged(Editable s) {
        // Save state here.
    }
});

Declara la devolución de llamada en tu Activity o Fragment. Usa un ViewModel para almacenar el estado.

3. Agrega el estado del elemento de lista a Adapter

Agrega el estado de los elementos de lista a tu RecyclerView.Adapter. Pasa el estado del elemento al constructor del adaptador cuando se cree el host Activity o Fragment:

Kotlin

val adapter = MyAdapter(items, viewModel.retrieveState())

Java

MyAdapter adapter = new MyAdapter(items, viewModel.retrieveState());

4. Recupera el estado del elemento de lista en el ViewHolder del adaptador

En RecyclerView.Adapter, cuando vinculas un ViewHolder a un elemento, restablece el estado del elemento:

Kotlin

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
    ...
    val item = items[position]
    val state = states.firstOrNull { it.item == item }

    if (state != null) {
        holder.restore(state)
    }
}

Java

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
    ...
    Item item = items[position];
    Arrays.stream(states).filter(state -> state.item == item)
        .findFirst()
        .ifPresent(state -> holder.restore(state));
}

Resultados

Tu RecyclerView ahora puede restablecer su posición de desplazamiento y el estado de cada elemento de la lista RecyclerView.

Recursos adicionales

Administración de teclado desmontable

Calificación de tres estrellas

La compatibilidad con teclados desmontables ayuda a maximizar la productividad del usuario en dispositivos con pantalla grande. Android activa un cambio de configuración cada vez que se conecta un teclado a un dispositivo o se desconecta de este, lo que puede causar la pérdida del estado de la IU. Tu app puede guardar y restablecer su estado, lo que permite que el sistema controle la recreación de actividad o restringir la recreación de actividades para los cambios de configuración del teclado. En todos los casos, todos los datos relacionados con el teclado se almacenan en un objeto Configuration. Los campos keyboard y keyboardHidden del objeto de configuración contienen información sobre el tipo de teclado y su disponibilidad.

Prácticas recomendadas

Las apps optimizadas para pantallas grandes admiten todo tipo de dispositivo de entrada, desde teclados en software y hardware hasta pluma stylus, mouse, panel táctil y otros dispositivos periféricos.

La compatibilidad con teclados externos implica cambios de configuración, que puedes administrar de dos maneras:

  1. Deja que el sistema vuelva a crear la actividad que se está ejecutando y tú te ocuparás de administrar el estado de tu app.
  2. Administra el cambio de configuración por tu cuenta (no se volverá a crear la actividad):
    • Declara todos los valores de configuración relacionados con el teclado
    • Crea un controlador de cambios de configuración

Las apps de productividad, que a menudo requieren un control detallado de la IU para la entrada de texto y otras entradas, pueden beneficiarse del enfoque de hazlo tú mismo para manejar los cambios de configuración.

En casos especiales, tal vez quieras cambiar el diseño de tu app cuando se conecta o desconecta un teclado de hardware, por ejemplo, a fin de dejar más espacio para herramientas o editar ventanas.

Dado que la única forma confiable de escuchar los cambios de configuración es anular el método onConfigurationChanged() de una vista, puedes agregar una nueva instancia de View a la actividad de tu app y responder en el controlador onConfigurationChanged() de la vista a los cambios de configuración causados por la conexión o desvinculación del teclado.

Ingredientes

  • android:configChanges: Es el atributo del elemento <activity> del manifiesto de la app. Informa al sistema sobre los cambios de configuración que administra la app.
  • View#onConfigurationChanged(): Es el método que reacciona a la propagación de una configuración nueva de la app.

Pasos

Resumen

Declara el atributo configChanges y agrega valores relacionados con el teclado. Agrega un View a la jerarquía de vistas de la actividad y detecta los cambios en la configuración.

1. Declara el atributo configChanges

Para actualizar el elemento <activity> en el manifiesto de la app, agrega los valores keyboard|keyboardHidden a la lista de cambios de configuración ya administrados:

<activity
      …
      android:configChanges="...|keyboard|keyboardHidden">

2. Cómo agregar una vista vacía a la jerarquía de vistas

Declara una vista nueva y agrega el código de tu controlador dentro del método onConfigurationChanged() de la vista:

Kotlin

val v = object : View(this) {
  override fun onConfigurationChanged(newConfig: Configuration?) {
    super.onConfigurationChanged(newConfig)
    // Handler code here.
  }
}

Java

View v = new View(this) {
    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        // Handler code here.
    }
};

Resultados

Tu app ahora responderá cuando se conecte o desconecte un teclado externo sin recrear la actividad que se esté ejecutando.

Recursos adicionales

Para aprender a guardar el estado de la IU de tu app durante cambios de configuración, como la conexión o la desconexión del teclado, consulta Cómo guardar estados de la IU.