Administración del enfoque del teclado en Compose

1. Introducción

Los usuarios pueden interactuar con tu app con un teclado físico, por lo general, en dispositivos con pantalla grande, como tablets y dispositivos ChromeOS, pero también en dispositivos de realidad extendida. Es importante que los usuarios puedan navegar por tu app con la misma eficacia con un teclado físico que con una pantalla táctil. Además, cuando diseñes tu app para pantallas de TV y automóviles, que pueden no tener entrada táctil y, en su lugar, depender de pads direccionales o codificadores rotativos, debes aplicar principios de navegación del teclado similares.

Compose te permite controlar las entradas de teclados físicos, pads direccionales y codificadores rotativos de forma unificada. Un principio clave de una buena experiencia del usuario para estos métodos de entrada es que los usuarios puedan mover el enfoque del teclado de forma intuitiva y coherente al componente interactivo con el que quieren interactuar.

En este codelab, aprenderás lo siguiente:

  • Implementar patrones comunes de administración de enfoque del teclado para una navegación intuitiva y coherente
  • Probar si el movimiento del enfoque del teclado se comporta como se espera

Requisitos previos

  • Experiencia en el desarrollo de apps con Compose
  • Conocimientos básicos de Kotlin, incluidas lambdas y corrutinas

Qué crearás

Implementarás los siguientes patrones típicos de administración de enfoque del teclado:

  • Movimiento del enfoque del teclado: De principio a fin, de arriba abajo en el patrón en forma de Z
  • Enfoque inicial lógico: Establece el enfoque en el elemento de la IU con el que es probable que el usuario interactúe.
  • Restauración del enfoque: Mueve el enfoque al elemento de la IU con el que el usuario interactuó anteriormente.

Qué aprenderás

  • Aspectos básicos de la administración de enfoque en Compose
  • Cómo establecer un elemento de la IU como objetivo de enfoque
  • Cómo solicitar el enfoque para mover un elemento de la IU
  • Cómo mover el enfoque del teclado a un elemento de la IU específico en un grupo de elementos de la IU

Requisitos

  • Android Studio Ladybug o una versión posterior
  • Cualquiera de los siguientes dispositivos para ejecutar la app de ejemplo:
  • Un dispositivo con pantalla grande y un teclado físico
  • Un dispositivo virtual de Android para dispositivos con pantalla grande, como el emulador con cambio de tamaño

2. Configuración

  1. Clona el repositorio de GitHub de codelabs de pantallas grandes:
git clone https://github.com/android/large-screen-codelabs

Otra opción es descargar y desarchivar el archivo ZIP de codelabs de pantallas grandes:

Descargar código fuente

  1. Navega a la carpeta focus-management-in-compose.
  2. En Android Studio, abre el proyecto. La carpeta focus-management-in-compose contiene un proyecto.
  3. Si no tienes una tablet Android, un dispositivo plegable o un dispositivo ChromeOS con un teclado físico, abre Device Manager en Android Studio y, luego, crea el dispositivo de tamaño variable en la categoría Phone.

El Administrador de dispositivos de Android Studio muestra la lista de dispositivos virtuales disponibles en la categoría de teléfonos. El emulador que puede cambiar de tamaño se encuentra en esta categoría.Figura 1: Cómo configurar el emulador de tamaño variable en Android Studio

3. Explora el código de partida

El proyecto tiene dos módulos:

  • start: Contiene el código de partida del proyecto. Realiza cambios en esta rama para completar el codelab.
  • solution: Contiene el código completo para este codelab.

La app de ejemplo consta de tres pestañas:

  • Objetivo de enfoque
  • Orden de recorrido del enfoque
  • Grupo de enfoque

La pestaña de objetivo de enfoque se muestra cuando se inicia la app.

La primera vista de la app de ejemplo. Tiene tres pestañas, y la pestaña de objetivo de enfoque, la primera, está seleccionada. La pestaña muestra tres tarjetas en una columna.

Figura 2: La pestaña Objetivo de enfoque se muestra cuando se inicia la app.

El paquete ui contiene el siguiente código de IU con el que interactúas:

4. Objetivo de enfoque

Un objetivo de enfoque es un elemento de la IU al que se puede mover el enfoque del teclado. Los usuarios pueden mover el enfoque del teclado con la tecla Tab o las teclas direccionales (flechas):

  • Tecla Tab: El enfoque se mueve al siguiente objetivo de enfoque o al anterior de manera unidimensional.
  • Teclas de dirección: El enfoque puede moverse en dos dimensiones: arriba, abajo, izquierda y derecha.

Las pestañas son objetivos de enfoque. En la app de ejemplo, el fondo de las pestañas se actualiza visualmente cuando la pestaña adquiere el enfoque.

El archivo de animación GIF muestra cómo el enfoque del teclado se mueve por los elementos de la IU. Se mueve por las tres pestañas y, luego, se enfoca la primera tarjeta.

Figura 3: El fondo del componente cambia cuando el enfoque se mueve a un objetivo de enfoque.

Los elementos interactivos de la IU son objetivos de enfoque de forma predeterminada

Un componente interactivo es un objetivo de enfoque de forma predeterminada. En otras palabras, el elemento de la IU es un objetivo de enfoque si los usuarios pueden presionarlo.

La app de ejemplo tiene tres tarjetas en la pestaña Objetivo de enfoque. La primera tarjeta y la tercera tarjeta son objetivos de enfoque, pero la segunda tarjeta no lo es. El fondo de la tercera tarjeta se actualiza cuando el usuario mueve el enfoque de la primera tarjeta con la tecla Tab.

La animación GIF muestra el movimiento inicial del enfoque del teclado en la pestaña de objetivo de enfoque. Omite la segunda tarjeta y se mueve a la tercera tarjeta desde la primera cuando el usuario presiona la tecla Tab en la primera tarjeta.

Figura 4: Los objetivos de enfoque de la aplicación excluyen la segunda tarjeta.

Modifica la segunda tarjeta para que sea un objetivo de enfoque

Para hacer que la segunda tarjeta sea un objetivo de enfoque, cámbiala a un elemento interactivo de la IU. La forma más fácil es usar el modificador clickable de la siguiente manera:

  1. Abre FocusTargetTab.kt en el paquete tabs
  2. Modifica el elemento SecondCard componible con el modificador clickable de la siguiente manera:
@Composable
fun FocusTargetTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
        SecondCard(
            modifier = Modifier
                .width(240.dp)
                .clickable(onClick = onClick)
        )
        ThirdCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
    }
}

Ejecución

Ahora, el usuario puede mover el enfoque a la segunda tarjeta, además de la primera tarjeta y la tercera tarjeta. Puedes probarlo en la pestaña Objetivo de enfoque. Confirma que puedes mover el enfoque de la primera tarjeta a la segunda tarjeta con la tecla Tab.

La animación GIF muestra el movimiento del enfoque del teclado después de la modificación. Se mueve de la primera tarjeta cuando el usuario presiona la tecla Tab en la primera tarjeta.

Figura 5: Se mueve el enfoque de la primera tarjeta a la segunda tarjeta con la tecla Tab.

5. Navegación de enfoque en un patrón en forma de Z

Los usuarios esperan que el enfoque del teclado se mueva de izquierda a derecha y de arriba abajo en la configuración de idioma de izquierda a derecha. Este orden de recorrido del enfoque se denomina patrón en forma de Z.

Sin embargo, Compose ignora el diseño cuando determina el siguiente objetivo de enfoque de la tecla Tab y, en su lugar, usa el recorrido de enfoque unidimensional según el orden de las llamadas a funciones componibles.

Recorrido de enfoque unidimensional

El orden de recorrido de enfoque unidimensional proviene del orden de las llamadas a funciones de componibilidad en lugar del diseño de la app.

En la app de ejemplo, el enfoque se mueve en el siguiente orden en la pestaña orden de recorrido de enfoque:

  1. Primera tarjeta
  2. Cuarta tarjeta
  3. Tercera tarjeta
  4. Segunda tarjeta

La animación GIF muestra que el enfoque del teclado se mueve de manera diferente a las expectativas del usuario.  Se mueve de la primera tarjeta a la tercera, luego a la cuarta y a la segunda. Puede ir en contra de la expectativa del usuario.

Figura 6: El recorrido del enfoque sigue el orden de las funciones de componibilidad.

La función FocusTraversalOrderTab implementa la pestaña de recorrido de enfoque de la app de ejemplo. La función llama a funciones componibles para las tarjetas: FirstCard, FourthCard, ThirdCard y SecondCard, en ese orden.

@Composable
fun FocusTraversalOrderTab(
    modifier: Modifier = Modifier
) {
    Row(
        horizontalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        Column(
            verticalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            FirstCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier
                    .width(240.dp)
                    .offset(x = 256.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier
                    .width(240.dp)
                    .offset(y = (-151).dp)
            )
        }
        SecondCard(
            modifier = Modifier.width(240.dp)
        )
    }
}

Movimiento de enfoque en un patrón en forma de Z

Puedes integrar el movimiento de enfoque en forma de Z en la pestaña orden de recorrido de enfoque de la app de ejemplo siguiendo estos pasos:

  1. Abre tabs.FocusTraversalOrderTab.kt
  2. Quita el modificador de offset de los elementos ThirdCard y FourthCard componibles.
  3. Cambia el diseño de la pestaña a una columna con dos filas de la fila actual con dos columnas.
  4. Mueve los elementos componibles FirstCard y SecondCard a la primera fila.
  5. Mueve los elementos componibles ThirdCard y FourthCard a la segunda fila.

El código modificado es el siguiente:

@Composable
fun FocusTraversalOrderTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            FirstCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp),
            )
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
        }
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(240.dp)
            )
        }
    }
}

Ejecución

Ahora, el usuario puede mover el enfoque de derecha a izquierda y de arriba abajo en el patrón en forma de Z. Puedes probarlo en la pestaña orden de recorrido de enfoque y confirmar que el enfoque se mueve en el siguiente orden con la tecla Tab:

  1. Primera tarjeta
  2. Segunda tarjeta
  3. Tercera tarjeta
  4. Cuarta tarjeta

En la animación GIF, se muestra cómo se mueve el enfoque del teclado después de la modificación. Se mueve de izquierda a derecha y de arriba abajo en orden z.

Figura 7: Navegación de enfoque en un patrón en forma de Z.

6: focusGroup

El enfoque se mueve de la primera tarjeta a la tercera tarjeta con la tecla de dirección right en la pestaña Grupo de enfoque. Es probable que el movimiento sea un poco confuso para los usuarios, ya que las dos tarjetas no están una al lado de la otra.

La animación del GIF muestra cómo el enfoque del teclado se mueve de la primera tarjeta a la tercera con la tecla de dirección correcta. Estas dos tarjetas se colocan en filas diferentes.

Figura 8: Se produce un movimiento de enfoque inesperado de la primera tarjeta a la tercera tarjeta.

La navegación de enfoque de dos dimensiones hace referencia a la información de diseño.

Si presionas una tecla de dirección, se activa el recorrido de enfoque en dos dimensiones. Este es un recorrido de enfoque común en las TVs, ya que los usuarios interactúan con tu app con un pad direccional. Si presionas las teclas de flecha del teclado, también se activa el recorrido de enfoque en dos dimensiones, ya que imitan la navegación con un pad direccional.

En el recorrido de enfoque en dos dimensiones, el sistema se refiere a la información geométrica de los elementos de la IU y determina el objetivo de enfoque para mover el enfoque. Por ejemplo, el enfoque se mueve a la primera tarjeta desde la pestaña de destino de enfoque con la tecla de dirección down, y presionar la tecla de dirección hacia arriba mueve el enfoque a la pestaña de objetivo de enfoque.

En el GIF, se muestra que el enfoque se mueve a la primera tarjeta de la pestaña de destino de enfoque con la tecla de dirección hacia abajo y, luego, vuelve a la pestaña con la tecla de dirección hacia arriba. Estos dos objetivos de enfoque son los más cercanos verticalmente.

Figura 9: Desplazamiento del enfoque con teclas de dirección hacia abajo y hacia arriba

El recorrido del enfoque en dos dimensiones no se une, a diferencia del recorrido de enfoque en una dimensión con la tecla Tab. Por ejemplo, el usuario no puede mover el enfoque con la tecla de flecha hacia abajo cuando se enfoca la segunda tarjeta.

En el GIF, se muestra que el enfoque permanece en la segunda tarjeta, incluso si el usuario presiona la tecla de dirección hacia abajo, ya que no hay un objetivo de enfoque debajo de la tarjeta.

Figura 10: La tecla de flecha hacia abajo no puede mover el enfoque cuando se enfoca la segunda tarjeta.

Los objetivos de enfoque están en el mismo nivel

En el siguiente código, se implementa la pantalla mencionada anteriormente. Hay cuatro objetivos de enfoque: FirstCard, SecondCard, ThirdCard y FourthCard. Estos cuatro objetivos de enfoque están en el mismo nivel, y ThirdCard es el primer elemento a la derecha de FirstCard en el diseño. Es por eso que el enfoque se mueve a la tercera tarjeta desde la primera tarjeta con la tecla de dirección right.

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

Agrupa los destinos de enfoque con el modificador focusGroup

Puedes cambiar el movimiento de enfoque confuso con los siguientes pasos:

  1. Abre tabs.FocusGroup.kt
  2. Modifica la función de componibilidad Column en la función de componibilidad FocusGroupTab con el modificador focusGroup.

El código actualizado es el siguiente:

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            modifier = Modifier.focusGroup(),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

El modificador focusGroup crea un grupo de enfoque que consta de los objetivos de enfoque dentro del componente modificado. Los objetivos de enfoque dentro del grupo de enfoque y los que están fuera de él se encuentran en diferentes niveles, y no hay un objetivo de enfoque ubicado en el lado derecho del elemento componible FirstCard. Como resultado, el enfoque no se mueve a ninguna tarjeta de la primera tarjeta con la tecla de dirección right.

Ejecución

Ahora, el enfoque no se mueve a la tercera tarjeta desde la primera tarjeta con la tecla de dirección right en la pestaña Grupo de enfoque de la app de ejemplo.

7. Solicita enfoque

Los usuarios no pueden usar teclados ni pads direccionales para seleccionar elementos arbitrarios de la IU con los que interactuar. Los usuarios deben mover el enfoque del teclado a un componente interactivo antes de interactuar con el elemento.

Por ejemplo, los usuarios deben mover el enfoque de la pestaña Objetivo de enfoque a la primera tarjeta antes de interactuar con ella. Puedes reducir la cantidad de acciones para iniciar la tarea principal del usuario si configuras lógicamente el enfoque inicial.

La animación GIF muestra que el usuario debe presionar la tecla Tab tres veces después de seleccionar la pestaña para mover el enfoque del teclado a la primera tarjeta de la pestaña.

Figura 11: Tres presiones de la tecla Tab desplazan el enfoque a la primera tarjeta.

Cómo solicitar enfoque con FocusRequester

Puedes solicitar el enfoque para mover un elemento de la IU con FocusRequester. Se debe asociar un objeto FocusRequester con un elemento de la IU antes de llamar al método requestFocus().

Establece el enfoque inicial en la primera tarjeta

Puedes establecer el enfoque inicial en la primera tarjeta con los siguientes pasos:

  1. Abre tabs.FocusTarget.kt
  2. Declara el valor firstCard en la función de componibilidad FocusTargetTab y, luego, inicialízalo con un objeto FocusRequester que se devuelve de la función remember.
  3. Modifica la función de componibilidad FirstCard con el modificador focusRequester.
  4. Especifica el valor firstCard como el argumento del modificador focusRequester.
  5. Llama a la función de componibilidad LaunchedEffect con el valor Unit y llama al método requestFocus() sobre el valor firstCard en la expresión lambda pasada a la función de componibilidad LaunchedEffect.

Se crea un objeto FocusRequester y se asocia con un elemento de la IU en el segundo y tercer paso. En el quinto paso, se solicita el enfoque para que se mueva al elemento de la IU asociado cuando se compone el elemento componible FocusdTargetTab por primera vez.

El código actualizado se verá de la siguiente manera:

@Composable
fun FocusTargetTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    val firstCard = remember { FocusRequester() }

    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier
                .width(240.dp)
                .focusRequester(focusRequester = firstCard)
        )
        SecondCard(
            modifier = Modifier
                .width(240.dp)
                .clickable(onClick = onClick)
        )
        ThirdCard(
            onClick = onClick,
            modifier = Modifier.width(240.dp)
        )
    }

    LaunchedEffect(Unit) {
        firstCard.requestFocus()
    }
}

Ejecución

Ahora, el enfoque del teclado se mueve a la primera tarjeta en la pestaña Objeto de enfoque cuando se selecciona la pestaña. Para probarlo, cambia de pestaña. Además, se selecciona la primera tarjeta cuando se inicia la app.

La animación GIF muestra que el enfoque del teclado se mueve automáticamente a la primera tarjeta cuando el usuario selecciona la pestaña de objetivo de enfoque.

Figura 12: El enfoque se mueve a la primera tarjeta cuando se selecciona la pestaña Objetivo de enfoque.

8. Mueve el enfoque a la pestaña seleccionada

Puedes especificar el objetivo de enfoque cuando el enfoque del teclado ingresa a un grupo de enfoque. Por ejemplo, puedes mover el enfoque a la pestaña seleccionada cuando el usuario lo mueve a la fila de pestañas.

Puedes implementar este comportamiento con los siguientes pasos:

  1. Abre App.kt.
  2. Declara el valor focusRequesters en la función de componibilidad App.
  3. Inicializa el valor focusRequesters con el valor que se muestra de la función remember, que muestra una lista de objetos FocusRequester. La longitud de la lista que se muestra debe ser igual a la de Screens.entries.
  4. Asocia cada objeto FocusRequester del valor focusRequester con el elemento Tab componible modificando el elemento Tab componible con el modificador focusRequester.
  5. Modifica el elemento componible PrimaryTabRow con el modificador focusProperties y el modificador focusGroup.
  6. Pasa una lambda al modificador focusProperties y asocia la propiedad enter con otra lambda.
  7. Muestra FocusRequester, que está indexado con el valor selectedTabIndex en el valor focusRequesters, desde la lambda asociada con la propiedad enter.

El código modificado se ve de la siguiente manera:

@Composable
fun App(
    modifier: Modifier = Modifier,
) {
    val context = LocalContext.current

    var selectedScreen by rememberSaveable { mutableStateOf(Screen.FocusTarget) }
    val selectedTabIndex = Screen.entries.indexOf(selectedScreen)
    val focusRequesters = remember {
        List(Screen.entries.size) { FocusRequester() }
    }

    Column(modifier = modifier) {
        PrimaryTabRow(
            selectedTabIndex = selectedTabIndex,
            modifier = Modifier
                .focusProperties {
                    enter = {
                        focusRequesters[selectedTabIndex]
                    }
                }
                .focusGroup()
        ) {
            Screen.entries.forEachIndexed { index, screen ->
                Tab(
                    selected = screen == selectedScreen,
                    onClick = { selectedScreen = screen },
                    text = { Text(stringResource(screen.title)) },
                    modifier = Modifier.focusRequester(focusRequester = focusRequesters[index])
                )
            }
        }
        when (selectedScreen) {
            Screen.FocusTarget -> {
                FocusTargetTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp),
                )
            }

            Screen.FocusTraversalOrder -> {
                FocusTraversalOrderTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp)
                )
            }

            Screen.FocusRestoration -> {
                FocusGroupTab(
                    onClick = context::onCardClicked,
                    modifier = Modifier.padding(32.dp)
                )
            }
        }
    }
}

Puedes controlar el movimiento del enfoque con el modificador focusProperties. En la lambda que se pasa al modificador, modifica FocusProperties, al que se hace referencia cuando el sistema elige el objetivo de enfoque cuando los usuarios presionan la tecla Tab o las teclas de dirección cuando el elemento de la IU modificado está enfocado.

Cuando configuras la propiedad enter, el sistema evalúa la lambda establecida en la propiedad y se mueve al elemento de la IU que está asociado con el objeto FocusRequester que muestra la lambda evaluada.

Ejecución

Ahora, el enfoque del teclado se mueve a la pestaña seleccionada cuando el usuario mueve el enfoque a la fila de pestañas. Puedes probarlo con los siguientes pasos:

  1. Ejecuta la app
  2. Selecciona la pestaña Grupo de enfoque.
  3. Mueve el enfoque a la primera tarjeta con la tecla de dirección down.
  4. Mueve el enfoque con la tecla de dirección up.

Figura 13: El enfoque se mueve a la pestaña seleccionada.

9. Restablecimiento del enfoque

Los usuarios esperan poder reanudar fácilmente una tarea cuando se interrumpe. El restablecimiento del enfoque admite la recuperación de una interrupción. El restablecimiento del enfoque mueve el enfoque del teclado al elemento de la IU que se seleccionó anteriormente.

Un caso de uso típico del restablecimiento del enfoque es la pantalla principal de las apps de transmisión de video. La pantalla tiene varias listas de contenido de video, como películas de una categoría o episodios de un programa de TV. Los usuarios exploran las listas y encuentran contenido interesante. A veces, los usuarios vuelven a la lista que examinaron anteriormente y continúan navegando por ella. Con el restablecimiento del enfoque, los usuarios pueden seguir navegando sin mover el enfoque del teclado al último elemento que vieron en la lista.

El modificador focusRestorer restablece el enfoque en un grupo focal.

Usa el modificador focusRestorer para guardar y restablecer el enfoque en un grupo de enfoque. Cuando el enfoque sale del grupo de enfoque, almacena una referencia al elemento que estaba enfocado anteriormente. Luego, cuando el enfoque vuelve a ingresar al grupo de enfoque, se restablece en el elemento enfocado anteriormente.

Integra el restablecimiento del enfoque con la pestaña del grupo de enfoque

La pestaña Grupo de enfoque de la app de ejemplo tiene una fila que contiene la segunda tarjeta, la tercera tarjeta y la cuarta tarjeta.

La animación GIF muestra que el enfoque del teclado se mueve de la primera tarjeta a la segunda, incluso si la tercera tarjeta está enfocada anteriormente.

Figura 14: Grupo de enfoque que contiene la segunda tarjeta, la tercera tarjeta y la cuarta tarjeta.

Puedes integrar el restablecimiento del enfoque en la fila con los siguientes pasos:

  1. Abre tab.FocusGroupTab.kt
  2. Modifica el elemento Row componible en el elemento FocusGroupTab componible con el modificador focusRestorer. Se debe llamar al modificador antes del modificador focusGroup.

El código modificado se ve de la siguiente manera:

@Composable
fun FocusGroupTab(
    onClick: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        verticalArrangement = Arrangement.spacedBy(16.dp),
        modifier = modifier,
    ) {
        FirstCard(
            onClick = onClick,
            modifier = Modifier.width(208.dp)
        )
        Row(
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            modifier = Modifier
                .focusRestorer()
                .focusGroup(),
        ) {
            SecondCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            ThirdCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
            FourthCard(
                onClick = onClick,
                modifier = Modifier.width(208.dp)
            )
        }
    }
}

Ejecución

Ahora, la fila de la pestaña Grupo de enfoque restablece el enfoque, y puedes probarlo con los siguientes pasos:

  1. Selecciona la pestaña Grupo de enfoque.
  2. Mueve el enfoque a la primera tarjeta
  3. Mueve el enfoque a la cuarta tarjeta con la tecla Tab
  4. Mueve el enfoque a la primera tarjeta con la tecla de dirección up
  5. Pulsa la tecla Tab

El enfoque del teclado se mueve a la cuarta tarjeta, ya que el modificador focusRestorer guarda la referencia de la tarjeta y restablece el enfoque cuando el enfoque del teclado ingresa al grupo de enfoque establecido en la fila.

La animación GIF muestra que el enfoque del teclado se mueve a la tarjeta seleccionada anteriormente en una fila cuando vuelve a ingresarlo.

Figura 15: El enfoque vuelve a la cuarta tarjeta después de presionar la tecla de dirección hacia arriba seguida de la tecla Tab.

10. Crea una prueba

Puedes probar la administración de enfoque del teclado implementada con pruebas. Compose proporciona una API para probar si un elemento de la IU está enfocado y presionar teclas en los componentes de la IU. Consulta el codelab Pruebas en Jetpack Compose para obtener más información.

Prueba la pestaña Objetivo de enfoque

En la sección anterior, modificaste la función de componibilidad FocusTargetTab para establecer la segunda tarjeta como objetivo de enfoque. Crea una prueba de la implementación que realizaste de forma manual en la sección anterior. La prueba se puede crear con los siguientes pasos:

  1. Abre FocusTargetTabTest.kt. Modificarás la función testSecondCardIsFocusTarget en los siguientes pasos.
  2. Para solicitar que el enfoque se mueva a la primera tarjeta, llama al método requestFocus en el objeto SemanticsNodeInteraction de la primera tarjeta.
  3. Asegúrate de que la primera tarjeta esté enfocada con el método assertIsFocused().
  4. Para presionar la tecla Tab, llama al método pressKey con el valor Key.Tab dentro de la lambda pasada al método performKeyInput.
  5. Para probar si el enfoque del teclado se mueve a la segunda tarjeta, llama al método assertIsFocused() sobre el objeto SemanticsNodeInteraction de la segunda tarjeta.

El código actualizado se verá de la siguiente manera:

@OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
@Test
fun testSecondCardIsFocusTarget() {
    composeTestRule.setContent {
        LocalInputModeManager
            .current
            .requestInputMode(InputMode.Keyboard)
        FocusTargetTab(onClick = {})
    }
    val context = InstrumentationRegistry.getInstrumentation().targetContext

    // Ensure the 1st card is focused
    composeTestRule
        .onNodeWithText(context.getString(R.string.first_card))
        .requestFocus()
        .performKeyInput { pressKey(Key.Tab) }

    // Test if focus moves to the 2nd card from the 1st card with Tab key
    composeTestRule
        .onNodeWithText(context.getString(R.string.second_card))
        .assertIsFocused()
}

Ejecución

Para ejecutar la prueba, haz clic en el ícono triangular que se muestra a la izquierda de la declaración de la clase FocusTargetTest. Consulta la sección Cómo ejecutar pruebas en Cómo realizar pruebas en Android Studio para obtener más información.

Android Studio muestra un menú contextual para ejecutar "FocusTargetTabTest".

11. Felicitaciones

¡Bien hecho! Aprendiste los componentes básicos para la administración del enfoque del teclado:

  • Objetivo de enfoque
  • Recorrido de enfoque

Puedes controlar el orden de recorrido del enfoque con los siguientes modificadores de Compose:

  • El modificador focusGroup
  • El modificador focusProperties

Implementaste el patrón típico de UX con teclado físico, enfoque inicial y restablecimiento del enfoque. Estos patrones se implementan combinando las siguientes APIs:

  • Clase FocusRequester
  • El modificador focusRequester
  • El modificador focusRestorer
  • Función de componibilidad LaunchedEffect

La UX implementada se puede probar con pruebas de instrumentación. Compose ofrece formas de presionar teclas y probar si un SemanticsNode tiene el enfoque del teclado o no.

Más información