Cómo cambiar el comportamiento del enfoque

A veces es necesario anular el comportamiento de foco predeterminado de los elementos en la pantalla. Por ejemplo, es posible que quieras agrupar los elementos componibles, evitar que Enfocarse en un elemento componible determinado de forma explícita foco de solicitud en uno captura o libera el foco, o redirecciona el foco en la entrada o salida. Esta se describe cómo cambiar el comportamiento del enfoque cuando los valores predeterminados no son los que que necesitan tus usuarios.

Brinda una navegación coherente con grupos focales

A veces, Jetpack Compose no adivina de inmediato el siguiente elemento correcto para navegación por pestañas, en especial cuando el elemento superior Composables complejo, como las pestañas y listas de aplicaciones.

Si bien la búsqueda de enfoque suele seguir el orden de declaración de Composables, Esto es imposible en algunos casos, como cuando uno de los Composables en la es un elemento desplazable horizontal que no es completamente visible. Esto se muestra en en el siguiente ejemplo.

Jetpack Compose puede decidir enfocar el siguiente elemento más cercano al inicio de la pantalla, como se muestra a continuación, en lugar de continuar la ruta que esperas para navegación unidireccional:

Animación de una app que muestra una navegación horizontal superior y una lista de elementos debajo.
Figura 1: Animación de una app que muestra una navegación horizontal superior y una lista de elementos debajo.

En este ejemplo, queda claro que los desarrolladores no pretendían enfocarse en Pasa de la pestaña Chocolates a la primera imagen a continuación y, luego, regresa a la La pestaña Repostería. En cambio, querían que el foco siguiera en las pestañas hasta que pestaña y, luego, enfócate en el contenido interno:

Animación de una app que muestra una navegación horizontal superior y una lista de elementos debajo.
Figura 2: Animación de una app que muestra una navegación horizontal superior y una lista de elementos debajo.

En situaciones en las que es importante que se enfoque un grupo de elementos componibles de forma secuencial, como en la fila Pestaña del ejemplo anterior, debes unir el Composable en un elemento superior que tiene el modificador focusGroup():

LazyVerticalGrid(columns = GridCells.Fixed(4)) {
    item(span = { GridItemSpan(maxLineSpan) }) {
        Row(modifier = Modifier.focusGroup()) {
            FilterChipA()
            FilterChipB()
            FilterChipC()
        }
    }
    items(chocolates) {
        SweetsCard(sweets = it)
    }
}

La navegación bidireccional busca el elemento componible más cercano para el elemento específico dirección, si un elemento de otro grupo está más cerca que un elemento no completamente visible elemento en el grupo actual, la navegación elige el más cercano. Para evitar esto, puedes aplicar el modificador focusGroup().

FocusGroup hace que un grupo completo parezca una sola entidad en términos de enfoque. pero el grupo en sí no será el foco, sino que el niño más cercano a conseguir el enfoque. De esta manera, la navegación reconoce que debe dirigirse a la parte elemento antes de abandonar el grupo.

En este caso, las tres instancias de FilterChip se enfocarán antes de SweetsCard elementos, incluso cuando SweetsCards son completamente visibles para la usuario y algunos FilterChip podrían estar ocultos. Esto sucede porque el El modificador focusGroup le indica al administrador de enfoque que ajuste el orden en el que los elementos estén enfocados para que la navegación sea más fácil y coherente con la IU.

Sin el modificador focusGroup, si FilterChipC no estaba visible, el foco la navegación lo recogería en último lugar. Sin embargo, agregar este tipo de modificador hace que no Solo es detectable, pero también adquirirá el foco justo después de FilterChipB, ya que que esperan los usuarios.

Haz que un elemento componible sea enfocable

Algunos elementos componibles son enfocables por diseño, como un elemento Button o un elemento componible con el modificador clickable adjunto. Si quieres agregar específicamente comportamiento enfocable en un elemento componible, usas el modificador focusable:

var color by remember { mutableStateOf(Green) }
Box(
    Modifier
        .background(color)
        .onFocusChanged { color = if (it.isFocused) Blue else Green }
        .focusable()
) {
    Text("Focusable 1")
}

Cómo hacer que un elemento componible no pueda enfocarse

Puede haber situaciones en las que algunos de tus elementos no deberían participar en primer plano. En estas raras ocasiones, puedes aprovechar la canFocus property para excluir un objeto Composable de modo que no se pueda enfocar.

var checked by remember { mutableStateOf(false) }

Switch(
    checked = checked,
    onCheckedChange = { checked = it },
    // Prevent component from being focused
    modifier = Modifier
        .focusProperties { canFocus = false }
)

Solicita el enfoque del teclado con FocusRequester

En algunos casos, es posible que quieras solicitar explícitamente el foco como respuesta a un la interacción del usuario. Por ejemplo, puedes preguntarle a un usuario si desea reiniciar el servicio completar un formulario y si presionan "sí" quieres volver a enfocar el primer campo de esa forma.

Lo primero que debes hacer es asociar un objeto FocusRequester con el elemento componible al que quieres mover el enfoque del teclado. En el siguiente código, se asocia un objeto FocusRequester con una TextField estableciendo un modificador llamado Modifier.focusRequester:

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

Puedes llamar al método requestFocus de FocusRequester para enviar solicitudes de enfoque reales. Debes invocar este método fuera de un contexto Composable. (de lo contrario, se vuelve a ejecutar en cada recomposición). El siguiente fragmento muestra cómo solicitar al sistema que mueva el enfoque del teclado cuando el botón se se hizo clic:

val focusRequester = remember { FocusRequester() }
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.focusRequester(focusRequester)
)

Button(onClick = { focusRequester.requestFocus() }) {
    Text("Request focus on TextField")
}

Captura y libera el enfoque

Puedes aprovechar el enfoque para guiar a los usuarios a fin de que proporcionen los datos correctos para tu aplicación. necesita realizar la tarea; por ejemplo, obtener una dirección de correo electrónico o un número de teléfono válidos de la fila. Si bien los estados de error informan a los usuarios sobre lo que está sucediendo, podría necesitar el campo con información errónea para mantenerse enfocado hasta que y solucionar el problema.

Para capturar el enfoque, puedes invocar el método captureFocus(). libérala después con el método freeFocus(), como en el siguiente ejemplo: ejemplo:

val textField = FocusRequester()

TextField(
    value = text,
    onValueChange = {
        text = it

        if (it.length > 3) {
            textField.captureFocus()
        } else {
            textField.freeFocus()
        }
    },
    modifier = Modifier.focusRequester(textField)
)

Prioridad de los modificadores de enfoque

Modifiers se puede ver como elementos que solo tienen un elemento secundario, por lo que, cuando pones en cola cada Modifier de la izquierda (o superior) une la Modifier que sigue en la derecha (o abajo). Esto significa que el segundo Modifier está contenido dentro la primera, de modo que, al declarar dos focusProperties, solo el elemento de más uno funciona, ya que los siguientes se encuentran en la parte superior.

Para aclarar más el concepto, consulta el siguiente código:

Modifier
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

En este caso, la focusProperties que indica item2 como el enfoque correcto No debe usarse, tal como se indica en el documento anterior. por lo tanto, item1 será el usar uno.

Con este enfoque, la madre o el padre también puede restablecer el comportamiento a la configuración predeterminada usando FocusRequester.Default:

Modifier
    .focusProperties { right = Default }
    .focusProperties { right = item1 }
    .focusProperties { right = item2 }
    .focusable()

No es necesario que el superior sea parte de la misma cadena de modificadores. Madre o padre puede reemplazar una propiedad de enfoque de un elemento componible secundario. Por ejemplo: considera este FancyButton que hace que el botón no sea enfocable:

@Composable
fun FancyButton(modifier: Modifier = Modifier) {
    Row(modifier.focusProperties { canFocus = false }) {
        Text("Click me")
        Button(onClick = { }) { Text("OK") }
    }
}

Para volver a seleccionar este botón, el usuario puede establecer canFocus en true:

FancyButton(Modifier.focusProperties { canFocus = true })

Como todos los Modifier, los relacionados con el enfoque se comportan de manera diferente según el orden. cuando las declaras. Por ejemplo, un código como el siguiente hace que Box pero la FocusRequester no está asociada con este enfocable, ya que se declara después del elemento enfocable.

Box(
    Modifier
        .focusable()
        .focusRequester(Default)
        .onFocusChanged {}
)

Es importante recordar que un focusRequester se asocia con el primer enfocable debajo de él en la jerarquía, por lo que este focusRequester apunta al primer hijo enfocable. En caso de que no haya ninguno disponible, no apuntará a nada. Sin embargo, dado que Box es enfocable (gracias al modificador focusable()), puedes navegar hasta allí mediante la navegación bidireccional.

Como otro ejemplo, funcionaría cualquiera de las siguientes opciones, ya que onFocusChanged() modificador hace referencia al primer elemento enfocable que aparece después de Modificadores focusable() o focusTarget()

Box(
    Modifier
        .onFocusChanged {}
        .focusRequester(Default)
        .focusable()
)
Box(
    Modifier
        .focusRequester(Default)
        .onFocusChanged {}
        .focusable()
)

Enfoque de redireccionamiento en la entrada o la salida

A veces, debes proporcionar un tipo de navegación muy específico, como la que como se muestra en la animación a continuación:

Animación de una pantalla que muestra dos columnas de botones colocadas una al lado de la otra y animando el enfoque de una columna a la otra.
Figura 3: Animación de una pantalla que muestra dos columnas de botones colocadas una al lado de la otra y animando el enfoque de una columna a la otra

Antes de profundizar en cómo crear esto, es importante comprender la configuración el comportamiento de la búsqueda de enfoque. Sin ninguna modificación, una vez que la búsqueda de enfoque llega al elemento Clickable 3 y presiona DOWN en el pad direccional (o su equivalente la tecla de flecha) moverá el enfoque a lo que se muestre debajo de Column, saliendo del grupo e ignorando el de la derecha. Si no hay elementos enfocables disponibles, el enfoque no se mueve a ningún lugar, sino que permanece Clickable 3

Para modificar este comportamiento y proporcionar la navegación prevista, puedes aprovechar las Modificador focusProperties, que te ayuda a administrar lo que sucede cuando el foco la búsqueda ingresa o sale de la Composable:

val otherComposable = remember { FocusRequester() }

Modifier.focusProperties {
    exit = { focusDirection ->
        when (focusDirection) {
            Right -> Cancel
            Down -> otherComposable
            else -> Default
        }
    }
}

Es posible dirigir el enfoque a una Composable específica cada vez que entra o sale de una cierta parte de la jerarquía, por ejemplo, cuando tu IU tiene dos y quiere asegurarse de que cuando se procese la primera el enfoque cambia al segundo:

Animación de una pantalla que muestra dos columnas de botones colocadas una al lado de la otra y animando el enfoque de una columna a la otra.
Figura 4: Animación de una pantalla que muestra dos columnas de botones colocadas una al lado de la otra y animando el enfoque de una columna a la otra

En este GIF, una vez que el enfoque llegue a Clickable 3 Composable en Column 1, el siguiente elemento que se enfoca es Clickable 4 en otro Column. Este comportamiento Se puede lograr combinando focusDirection con enter y exit. dentro del modificador focusProperties. Ambos necesitan una lambda que tome como parámetro, la dirección de la que proviene el foco y que devuelve un FocusRequester Esta lambda puede comportarse de tres maneras diferentes: mostrar FocusRequester.Cancel evita que el enfoque continúe, mientras FocusRequester.Default no altera su comportamiento. En cambio, se proporciona Un FocusRequester adjunto a otro Composable hace que el foco salte a ese elemento. Composable específica

Cambiar el enfoque de la dirección de avance

Para avanzar el enfoque al siguiente elemento o hacia una dirección precisa, puedes Aprovecha el modificador onPreviewKey e implica el LocalFocusManager para hacer avanzar el enfoque con el modificador moveFocus.

En el siguiente ejemplo, se muestra el comportamiento predeterminado del mecanismo de enfoque: cuando un elemento Se detecta la pulsación de teclas tab, el foco avanza al siguiente elemento lista. Aunque no es algo que normalmente tendrás que configurar, es importante el funcionamiento interno del sistema para poder cambiar la configuración de tu modelo.

val focusManager = LocalFocusManager.current
var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = { text = it },
    modifier = Modifier.onPreviewKeyEvent {
        when {
            KeyEventType.KeyUp == it.type && Key.Tab == it.key -> {
                focusManager.moveFocus(FocusDirection.Next)
                true
            }

            else -> false
        }
    }
)

En este ejemplo, la función focusManager.moveFocus() hace avanzar el enfoque a el elemento especificado o en la dirección implícita en el parámetro de la función.