Concéntrese en Jetpack Compose.Una breve introducción a las API de Focus en … | Jamie Sanson | Junio de 2021
Para empezar a utilizar realmente Compose se requiere un cambio de mentalidad. Cuando empiezas a pensar de la manera correcta, usar la IU declarativa puede ser muy rápido y beneficioso. Se necesita tiempo para entrenarse para deshacerse de los viejos hábitos y aprender a hacer las cosas nuevamente. La gestión del enfoque en Compose es un poco diferente a la que estamos acostumbrados en el viejo Android, ¡así que echemos un vistazo!
He estado trabajando por un tiempo Como elemento secundario en la aplicación de la lista de tareas pendientes. Esta aplicación puede hacer muchas cosas peculiares y su interfaz de usuario está completamente integrada en Compose.Ignore todas las cosas elegantes por ahora, vamos Enfocar Acerca de la experiencia principal de usar esta aplicación. Este es un fragmento de código que describe una lista básica de tareas pendientes.
LazyColumn
items(todos) todo ->
TodoRow(
text = todo.text,
isDone = todo.isDone,
callbacks = TodoCallbacks(/* For things a row can do */)
)
Me propuse mejorar la navegabilidad de esta lista esta mañana. Puede agregar y eliminar tareas pendientes de forma rápida y sencilla utilizando solo el teclado. Este gif muestra el tipo de comportamiento que busco, tomado de Google Keep.
En resumen, si estoy escribiendo algo que debe hacerse y presiono Intro, quiero que mi cursor se mueva a una nueva línea con un nuevo elemento de tarea pendiente. Si retrocedo completamente un elemento, quiero que se elimine y mi cursor se moverá a la línea superior. Obviamente, necesitamos administrar el enfoque, así que veamos nuestras opciones.
Compose UI incluye un FocusManager
Type, que le permite presionar el foco en una llamada, es muy adecuado para un recorrido de contenido simple.
Por ejemplo, puede tener una columna simple TextField
El formulario que desea explorar cuando el usuario presiona el botón «Siguiente» en el teclado. FocusManager
Atravesará su jerarquía de enfoque por usted y encontrará lo siguiente en lo que enfocarse en la dirección que necesita.Este es un código, con FocusDirection.Down
Hace que el foco se mueva al siguiente campo de la lista.
Column
val focusManager = LocalFocusManager.currentfor (i in 1..4)
TextField(
// ...
keyboardActions = KeyboardActions(
onNext =
focusManager.moveFocus(FocusDirection.Down)
)
)
Y si quieres Down
¿Representa algo diferente al siguiente elemento de la lista? ¿O quieres que tu combinación personalizada sea enfocable?Bueno, tienes suerte porque el modificador de enfoque admite FocusManager
Y se puede utilizar de cualquier forma.
Puede interactuar con el mecanismo de enfoque en Redactar a través de varios modificadores diferentes
Modifier.focusTarget()
– Esto le permite hacer que el componente sea enfocableModifier.focusOrder()
– CombinarFocusRequester
s, que le permite cambiar el orden de enfoqueModifier.focusRequester()
– Agregar personalizaciónFocusRequester
, Lo que le permite observar el estado de enfoque y solicitar el enfoque para un solo componenteModifier.onFocusEvent()
,Modifier.onFocusChanged()
– Es más fácil observar cambios internos o reales en el estado de enfoque.
Para entender cómo funcionan, veamos algunos ejemplos.
Observa el estado de enfoque onFocusChanged
onFocusChanged
Le permite reaccionar a los cambios de enfoque que afectan la composición o sus elementos secundarios:
TextField(
value = "My text field",
onValueChange = ,
modifier = Modifier.onFocusChanged focusState ->
when
focusState.isFocused ->
println("I'm focused!")
focusState.hasFocus ->
println("A child of mine has focus!")
)
Puedes pasar onFocusEvent
Modificador, emitirá un nuevo FocusState
Siempre que se escriba el estado de enfoque interno.
Logra el enfoque para diseños personalizados
Al implementar la capacidad de composición personalizada, puede centrarse en la interactividad. Bajo estas circunstancias, focusTarget()
¡Es tu amigo!
@Composable
fun CoolFocusableGraph(modifier: Modifier = Modifier)
// Make ensure our laid out component is focusable, and
// observe focus events to make it interactive
val customComponentModifier = modifier
.focusTarget() // Now focusable!
.onFocusEvent TODO("React to events")
.drawBehind TODO("Draw something cool") Layout(
content = ,
modifier = customComponentModifier,
measurePolicy = TODO()
)
Cambiar el orden de enfoque
En algunos casos, es posible que desee que el orden de enfoque sea diferente del orden predeterminado. En la mayoría de los casos, esto no es deseable, pero si lo necesita, siga los pasos a continuación. Un mal ejemplo podría ser omitir campos al completar datos válidos.
// First, get a reference to two focus requesters
val (first, second) = FocusRequester.createRefs()Column
// Down should take us to the third component
TextField(
...
modifier = Modifier.focusOrder(first) down = second
)
// Skip this one when moving in the "down" direction
TextField(...)
// Set the requester to tie them together
TextField(
...
modifier = Modifier.focusOrder(second)
)
Solicitar programáticamente el enfoque de un componente específico
¡Aún más indeseable es que puede elegir administrar el enfoque usted mismo! Si no tiene cuidado, esto puede ser una mala idea, porque es fácil pasar por alto las sutilezas de cómo funciona el enfoque transversal. Si es lo suficientemente cuidadoso, eventualmente volverá a implementar la lógica transversal del enfoque sin usar los componentes internos del enfoque, no es divertido.En términos generales, vale la pena intentar llevarlo a cuestas FocusManager
Mientras más, mejor.
Si esto es definitivamente algo que necesita hacer, puede usar FocusRequester
versus focusRequester()
Los modificadores solicitan mediante programación el enfoque de un componente específico.
// Focus could be part of your state
data class InputField(val text: String, val isFocused: Boolean)@Composable
fun InputRow(item: InputField)
val requester = FocusRequester()
TextField(
...
modifier = Modifier.focusRequester(requester)
)
// Request focus as a SideEffect (after the composition)
SideEffect
if (item.isFocused)
requester.requestFocus()
Ahora que hemos visto todos los puntos importantes, intentemos aplicarlo a nuestra pregunta de la lista de tareas pendientes.
La solución que quiero es FocusManager
, Estoy tratando de mover el enfoque hacia arriba o hacia abajo al agregar o eliminar elementos. Esto es muy efectivo para eliminar elementos, porque el siguiente objetivo de enfoque ya existe. Cuando la atención se centra en los elementos recién agregados, las cosas comienzan a complicarse un poco. Intenté mover el foco a un componente que no existe actualmente, dejándome en la línea donde comencé.
La solución en la que pensé primero fue intentar ejecutar focusManager.moveFocus
llamada SideEffect
-Un fragmento de código que se ejecuta después de cada combinación. Esto tiene sentido porque publico efectivamente el estado en algo externo, pero hace las cosas más complicadas porque solo quiero llamarlo una vez por cada elemento agregado a la lista. Lo que realmente quiero es cambiar el enfoque después de una combinación exitosa de cambios en la lista de elementos.Finalmente encontré una combinación LaunchedEffect
, con focusManager.moveFocus
Se llama para mover el foco solo cuando cambia la lista de elementos. El código completo se muestra a continuación.
@Composable
fun ListScreen(lists: List<TodoList>)
// Get a reference to the current FocusManager
val focusManager = LocalFocusManager.current
var focusDirectionToMove by remember mutableStateOf<FocusDirection?>(null) // Redux dispatch - stay tuned for a blog about this
val dispatch = LocalDispatch.current
// When add or remove events are dispatched, move the focus
val wrappedDispatch: (Any) -> Any = action ->
when (action)
is Action.AddTodo,
is Action.AddTodoAsSibling ->
focusDirectionToMove = FocusDirection.Down
is Action.DeleteTodo ->
focusDirectionToMove = FocusDirection.Up
dispatch(action)
// My list of items
TodoListColumn(lists, wrappedDispatch)
// If we've previously asked to move the focus, do it when
// the lists parameter changes
LaunchedEffect(lists)
focusDirectionToMove?.let(focusManager::moveFocus)
focusDirectionToMove = null