A medida que los nuevos factores de forma van más allá de la pantalla táctil, el sistema de entrada de Compose evoluciona para enfrentar el desafío. Su arquitectura en capas proporciona la base para admitir todo, desde gestos avanzados del panel táctil hasta entradas de hardware novedosas, sin problemas.
A través de desgloses arquitectónicos claros y ejemplos de código prácticos, sigue el ciclo de vida de un evento de entrada desde la capa de la plataforma del SO hasta los detectores de gestos y los componentes de la IU de Compose. Descubrirás cómo rastrear eventos en toda la pila, aplicar patrones de arquitectura clave para los tipos de entrada emergentes y depurar con confianza interacciones de gestos complejas en tus propias apps.
performTrackpadInput. Estas APIs proporcionan superficies de prueba conocidas para validar que las apps controlen correctamente los dispositivos que no son táctiles.Transcripción
Hi, everyone!
Esperamos que estés disfrutando de la Android Developer Summit y te agradecemos por asistir a nuestra charla.
Soy Alex Vanyo, ingeniero de software del equipo de Compose, y me acompaña Jeremy, también ingeniero de software del equipo de Compose.
Ambos trabajamos para mejorar el sistema de entrada de Jetpack Compose y, hoy, hablaremos sobre cómo lo escalamos más allá de la pantalla táctil para que sea más fácil admitir la entrada no táctil de forma inmediata.
En términos generales, la entrada captura cualquier forma en que un usuario interactúa con tu app.
Es probable que las pantallas táctiles sean lo primero que se te ocurra para una app para dispositivos móviles, pero definitivamente no son el único tipo de entrada.
Otros tipos de entrada incluyen teclados físicos, mouses, paneles táctiles, plumas stylus, controles y dispositivos giratorios.
También estamos viendo nuevos tipos de entrada, como los paneles táctiles en los lentes inteligentes, el monitoreo de manos y el seguimiento de ojos en la XR, y los gestos con los dedos y el gesto de muñeca en Wear.
Y si quieres ir aún más allá, también hay otros tipos de entrada, como los IME de software para la edición de texto, los micrófonos, las cámaras y otros sensores de dispositivos.
Debes esperar que tus apps se usen con una amplia variedad de tipos de entrada.
Una de las razones es la variedad cada vez mayor de dispositivos en los que se ejecutan las aplicaciones y la creciente adaptabilidad de esos dispositivos.
Los factores de forma de los dispositivos pueden tener un método de entrada más común, pero es importante no suponer que un factor de forma de dispositivo en particular implica el uso de un tipo de entrada específico.
Si bien una Chromebook tiene un panel táctil y un teclado integrados, eso no hace que esos tipos de entrada sean exclusivos de ese factor de forma.
También debes esperar que se usen teclados físicos, paneles táctiles y mouses en teléfonos, tablets y TVs, y algunas TVs incluso tienen pantallas táctiles.
Un segundo motivo importante para esperar y admitir varios tipos de entrada es la accesibilidad.
Permitir que los usuarios usen el dispositivo de entrada que prefieran y que lo cambien según lo consideren necesario es lo correcto para que las aplicaciones sean más accesibles y fáciles de usar.
Y, según la jurisdicción, puede ser un requisito legal tener un comportamiento específico y compatible en tu aplicación para estos tipos de entrada, en especial en lo que respecta a teclados y mouse.
En resumen, debes compilar compatibilidad con diferentes tipos de entrada, independientemente del factor de forma.
Veamos cómo Jetpack Compose ayuda a que tus apps satisfagan estas necesidades de los usuarios.
La filosofía de Compose, en general, es que estos diferentes tipos de entrada deben comportarse de forma adecuada de inmediato con los componentes estándar sin que tengas que hacer nada especial.
Además, deberías tener las herramientas que necesitas para personalizar y ajustar el comportamiento según lo consideres apropiado.
Por lo tanto, más allá de esos tipos de entrada iniciales, a medida que la plataforma subyacente admita más tipos de entrada, nuestro objetivo es crear compatibilidad con ellos en los componentes de Compose, lo que mejorará el comportamiento de tus aplicaciones con solo actualizar tu versión de Compose.
En esta charla, veremos dos de estos ejemplos, que son incorporaciones recientes a Compose.
Casualmente, ambos son dispositivos similares a un panel táctil, pero, como verás, se usan de formas muy diferentes.
Comencemos con un trabajo reciente sobre los paneles táctiles de las laptops.
Un panel táctil es un dispositivo de entrada basado en un puntero.
Más específicamente, cuando usas un panel táctil, obtienes coordenadas bidimensionales.
Sin embargo, a diferencia de una pantalla táctil, esas coordenadas de los dedos en el panel táctil no están directamente en el espacio de coordenadas de la IU.
En cambio, para un panel táctil similar al de una laptop, hay una traducción para controlar un cursor.
En esta traducción, puedes esperar una serie de conversiones de plataforma complicadas, como la aceleración del cursor y el rechazo de la palma.
Por lo tanto, lo que obtienes en el lado de Compose es un puntero que corresponde a la ubicación del cursor que suele actuar como una entrada del mouse.
Desde la plataforma de Android, obtenemos información de desplazamiento, clics, incluidos los clics secundarios o terciarios, y toda esta información se une en MotionEvents.
Para comprender cómo mejoramos el comportamiento en las versiones más recientes de Compose, primero veamos una descripción general de cómo Compose controla estos eventos de puntero con más detalle.
Dividiré esto en seis fases diferentes, y las analizaremos una por una.
La primera fase es el traslado de MotionEvents a Compose.
Por lo tanto, Compose es, en realidad, solo una vista: una vista muy complicada que hace mucho, pero es solo una vista, lo que significa que debe funcionar dentro del sistema general de Android para la entrada de puntero.
Por lo tanto, en Compose, como se encuentra dentro de una ventana normal en una actividad normal, recibe MotionEvents: los MotionEvents de la plataforma de Android.
Luego, Compose debe controlarlos y hacer todo lo que quieras hacer en Compose en relación con la entrada de puntero.
La primera fase consiste en traducir esos MotionEvents en una representación interna independiente de la plataforma en Compose, que llamamos PointerInputEvent.
Ese es un tipo interno, por lo que no lo verás directamente.
Si quieres ver todos los detalles de lo que sucede aquí, puedes consultar MotionEventAdapter, pero omitiremos esos detalles aquí.
Ahora, tenemos una representación independiente de la plataforma de un MotionEvent en Compose.
Luego, resulta que la entrada suele ocurrir con el tiempo.
Recibes muchos eventos de entrada uno tras otro, por lo que es muy útil saber qué sucede con el tiempo.
Los gestos, el seguimiento de la velocidad, la detección de la pendiente, todo eso necesita una comprensión de lo que sucede en cada momento.
Por lo tanto, la siguiente etapa en esta canalización de procesamiento es agregar esta información de tiempo, y se realizará un procesamiento previo basado en eventos de puntero sucesivos.
En el código, puedes ver esto como tomar el PointerInputEvent actual, combinarlo con lo que sucedió con el anterior y, luego, crear un InternalPointerEvent que tenga en cuenta el tiempo.
Este es otro tipo interno, como su nombre lo indica, pero puedes considerarlo como un mapa de diferentes IDs de puntero para los cambios de entrada del puntero, y PointerInputChanges es un tipo público.
Esa es la primera.
Y algo que podrías notar al pensar en diferentes IDs de puntero es que, ahora que conocemos el seguimiento del tiempo, también podemos hacer un seguimiento de cuándo entran y salen los IDs de puntero.
No hay solo una.
Si tienes varios dedos en una pantalla táctil, es posible que tengas varios punteros al mismo tiempo que van y vienen, y debemos controlar eso.
Nuevamente, si quieres profundizar en los detalles, puedes consultar PointerInputChangeEventProducer, que hace todo esto, pero lo omitiremos.
Tenemos mucho más por ver.
Ahora tenemos una representación de Compose de la entrada, y tiene información de tiempo.
El siguiente paso es determinar qué hacer con ese evento.
¿A dónde lo enviamos?
Y eso nos lleva a las pruebas de ruta de acceso de hits.
Imaginemos una situación en la que se envía un MotionEvent y este ComposeView llena toda la ventana, tal vez toda la pantalla.
Debemos averiguar a dónde enviarlo.
Y como Compose es solo una vista, pero tenemos muchísimas cosas dentro de Compose, un montón de nodos diferentes, botones, cosas con las que interactúas, necesitamos hacer en Compose todo el trabajo de averiguar dónde enviar un evento que ocurre en una posición específica y averiguar qué nodo está realmente interesado en él.
Estos nodos de diseño tienen coordenadas, límites y formas, por lo que debemos averiguar cuál de ellos, si es que hay alguno, se ve afectado.
Pero tampoco es la vista más general.
Resulta que nos gusta poner elementos interactivos dentro de otros elementos interactivos, como un botón dentro de una lista.
Resulta que lo hacemos mucho.
Por lo tanto, el caso general completo aquí es que un evento de entrada específico crea una ruta de hits completa que pasa por cada nodo interactivo que se ve afectado por las coordenadas del evento de entrada.
Por lo tanto, en este caso, hay tres niveles en la jerarquía de anidamiento.
Por lo tanto, tenemos una ruta de hit, pero eso ni siquiera es suficiente.
¿Recuerdas que dije que tenemos varios eventos de entrada de puntero, varios punteros que podrían estar presionados?
En realidad, necesitamos tener un árbol de ruta de acceso de hits completo.
No hay una sola ruta de hit cuando tienes varios punteros.
Esa es la siguiente fase: crear y actualizar este árbol de rutas de hits.
Veamos con más detalle lo que sucede.
A la izquierda, tenemos nodos que se disponen visualmente y, luego, en la parte inferior izquierda, puedes ver la representación jerárquica, qué nodos están dentro de otros nodos, cuáles son ancestros o descendientes entre sí.
Cuando un puntero baja, hacemos un seguimiento de esa ruta de posicionamiento, la ruta completa a través del árbol, y, luego, obtenemos una ruta de posicionamiento para ese puntero en particular.
Luego, esta ruta de acceso de posicionamiento se convierte en un árbol cuando obtenemos el segundo puntero.
Puede cruzarse con algunos de los mismos nodos que el otro, pero también puede interactuar con otros.
Por lo tanto, a la derecha, se encuentra el árbol de rutas de hits que mantenemos actualmente.
Y los punteros pueden ir y venir, por lo que debemos mantener esto a lo largo del tiempo para asegurarnos de obtener la lista completa de nodos que están interesados en cada evento en curso en este momento.
Bien.
Ahora tenemos un árbol de rutas de hits que mantenemos para estos eventos de entrada, lo que significa que pasamos a la siguiente fase.
Ahora, sabemos a dónde enviaremos estos eventos de entrada de puntero, qué nodos están interesados en cada evento específico.
Ahora que sabemos eso, podemos hacer una transformación de coordenadas.
Resulta que es muy bueno si tu nodo recibe eventos de entrada dentro de su propio espacio de coordenadas locales, por lo que no tienes que hacer un montón de, ya sabes, matemáticas de coordenadas por tu cuenta.
Una vez que sabemos eso, podemos tomar el evento de entrada del puntero, combinarlo con las coordenadas del nodo que nos interesa y, luego, esto finalmente crea ese PointerEvent público que se enviará a todos ustedes y al modificador de entrada del puntero.
Y eso es lo último de nuestra fase, la fase 6, que es enviar el evento.
Este es un evento de entrada de puntero que ocurrió y se envía al nodo para que haga lo que quiera con él.
Para ello, realizamos tres pasadas diferentes.
Tenemos un pase inicial, un pase principal y un pase final.
Y el motivo por el que hay tres pases diferentes es que permite que los nodos se comuniquen entre sí y comuniquen el interés en partes específicas de eventos de entrada de puntero específicos y, luego, consuman diferentes partes de ellos como parte del envío.
Por lo tanto, el primer paso, el paso inicial, va del ancestro al descendiente.
Un ejemplo de dónde es útil esto es si algún nodo de diseño externo puede ser un contenedor de desplazamiento, y sabe que comenzó a desplazarse y quiere evitar que todos los eventos de puntero lleguen a cualquiera de sus elementos secundarios.
Esto, en el pase inicial, permite que lo consuma y, luego, los elementos secundarios no recibirán más eventos.
El segundo pase es el principal.
Y esta es la única que va de descendiente a ancestro.
Como su nombre lo indica, este es el pase principal para hacer cosas.
Por ejemplo, aquí es donde un descendiente consumirá un evento de presión, lo que iniciará el control de un clic, y, luego, tendrá la oportunidad de hacerlo antes que cualquiera de sus elementos superiores si tienes elementos en los que se puede hacer clic anidados.
Y, luego, el último pase, el pase final, se ve igual que el pase inicial, en el que va del ancestro al descendiente.
Aquí es donde un elemento secundario puede indicarle a un elemento superior que este consumió parte de un evento.
Por lo tanto, si un elemento secundario comienza a controlar un clic, pero luego un elemento superior comienza a desplazarse, el elemento secundario puede ver que el elemento superior hizo eso y, luego, cancelar el clic que había comenzado a controlar.
Esos son los tres pases y las seis fases.
Esta es la base de todo lo que se encuentra sobre el sistema de entrada de puntero.
Todo el control de gestos, el desplazamiento, el arrastre, los clics y cualquier entrada de puntero personalizada se compilan sobre esta base.
Con todo eso, ahora podemos hablar un poco sobre los detalles de cómo mejoramos la compatibilidad con el comportamiento del panel táctil en Compose.
Por lo tanto, antes de Compose 1.12, los eventos de entrada del panel táctil se informaban como un tipo de puntero táctil, ya que es un dedo que interactúa físicamente con un panel táctil para controlar el cursor.
Sin embargo, la expectativa del usuario es que este tipo de entrada actúe mucho más como un mouse físico.
Por lo tanto, cambiamos el tipo de puntero de PointerType.Touch a PointerType.Mouse.
Con este cambio, los usuarios verán una experiencia de mouse y panel táctil mucho más alineada para cualquier componente que consulte esta información.
Un ejemplo de esto es el comportamiento de la información sobre la herramienta: es probable que quieras tener el mismo comportamiento para un panel táctil y un mouse para la información sobre la herramienta.
El segundo gran cambio que hicimos en el panel táctil se relaciona con los gestos de varios dedos que harías en un panel táctil.
Anteriormente, Compose pasaba por el concepto inteligente, pero un poco extraño, de dedos falsos de la plataforma de Android.
Veamos qué significa eso.
Cuando usas dos dedos en un panel táctil para desplazarte, Android genera un solo dedo de pantalla táctil falso que se desplaza hacia abajo donde está el cursor.
Luego, a medida que mueves los dedos en el panel táctil, el dedo falso en la pantalla táctil se mueve la distancia y la dirección correctas para imitar el comportamiento deseado y, luego, se suelta.
Por lo tanto, dos dedos que se mueven hacia arriba en el panel táctil se traducen en un solo dedo falso en la pantalla táctil, que también se mueve hacia arriba, lo que, a su vez, provoca un desplazamiento hacia abajo.
Un aspecto interesante aquí es que, si usas la función de desplazamiento inverso del panel táctil, como yo, querrás que el desplazamiento vaya en la otra dirección.
Y la forma en que se logra es invirtiendo la dirección hacia donde va el dedo falso.
Por lo tanto, ahora, dos dedos en el panel táctil que se mueven hacia arriba se traducen en un solo dedo falso en la pantalla táctil que se mueve hacia abajo, lo que, a su vez, provoca un desplazamiento hacia arriba, lo opuesto a lo que teníamos antes.
El escalamiento funciona de una manera similar.
Cuando usas dos dedos en un panel táctil para ajustar la escala, Android genera dos dedos de pantalla táctil falsos que bajan y, luego, se acercan o se alejan, según el gesto, para imitar el comportamiento deseado de acercar o alejar.
Por lo tanto, desde Compose 1.12 para el desplazamiento lateral y Compose 1.13 para el escalamiento, ahora interceptamos esos dedos falsos.
No los enviamos tal como nos los proporciona la plataforma, sino que los reinterpretamos en nuevos tipos de eventos de puntero: PanStart, PanMove y PanEnd para el desplazamiento lateral, y ScaleStart, ScaleChange y ScaleEnd para el escalamiento.
Y lo hacemos en esa fase de conversión de eventos de Android a Compose de la que hablé antes.
Por lo tanto, convertimos los eventos de Fake Finger en sus tipos adecuados y, luego, enviamos estos gestos definidos por la plataforma a la ruta de Hit que corresponde al cursor.
Veamos por qué es importante.
¿Por qué hacemos esto?
Supongamos que este cuadro es una imagen que queremos poder ampliar.
Cuando usas dos dedos en un panel táctil para escalar, es posible que uno de los dos dedos falsos no toque el elemento.
Su ruta de hit no se cruzaría con la imagen y comenzaría fuera de los límites.
Por lo tanto, en lugar de escalar, esto haría que la imagen se desplace.
Luego, si hubiera otro elemento desplazable fuera de la imagen, tal vez ese también comenzaría a desplazarse junto con ella.
Esto no es lo que quería el usuario.
Querían escalar.
Por lo tanto, lo que hacemos ahora es, en lugar de enviar esos dedos falsos, enviamos el gesto de escalamiento solo al elemento que se encuentra debajo de la posición del cursor, lo que genera el comportamiento correcto.
No importa dónde esté el cursor ni qué tan cerca esté del borde del elemento que quieres escalar, ahora se escalará correctamente.
Por lo tanto, la buena noticia para todos ustedes es que estos eventos de entrada reinterpretados se usan de forma predeterminada en componentes como scrollable y transformable para brindar un comportamiento de mayor fidelidad para las entradas del panel táctil si usan esos componentes básicos.
Y si usas algo que se haya creado sobre ellos, solo obtendrás ese comportamiento mejorado si actualizas tu versión de Compose a la última versión.
Y si escribes modificadores de entrada de puntero personalizados, te recomiendo que consultes estas nuevas APIs y te asegures de que las admites para brindarles a tus usuarios la mejor experiencia posible en todos estos diferentes dispositivos de entrada.
Y, por supuesto, también agregamos APIs de prueba, para que puedas verificar que tu app controle correctamente todos estos tipos de entrada diferentes.
Por lo tanto, la performTrackpadInput se parece mucho a las APIs de prueba existentes para el mouse y la pantalla táctil.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}Y, con todo eso, puedes validar que tu app funcione correctamente, sin importar qué dispositivo de entrada use el usuario en tu dispositivo.
Esperamos que esto te dé una mejor idea de cómo un evento de puntero va desde un dedo en un panel táctil hasta tu app.
Ahora, le daré la palabra a Jeremy para que hable sobre una forma muy diferente en que se usan los paneles táctiles.
Gracias.
Bien.
Hola, soy Jeremy Walker.
Trabajo mucho en la entrada y pensé que sería interesante contarte algunas de las novedades que estamos haciendo con la entrada de Compose y cómo te afecta.
Y, específicamente, con este tipo de entrada indirecta, que surgió debido a una nueva entrada basada en un nuevo factor de forma: los lentes inteligentes.
Entonces, cuando recibes información de los lentes, puedes hacerlo de varias maneras: puedes mirar algo, puedes usar la voz, puedes usar gestos, pero voy a hablar específicamente del panel táctil.
Y estos son un poco diferentes de los que habló Alex, en el sentido de que están, como, en el costado de la cabeza, son verticales, son muy anchos y son extremadamente cortos.
Entonces, cuando pensamos en cómo funcionaría esto en Compose, si imaginas que estás mirando un botón pequeño frente a ti, si mueves un cursor para intentar tocarlo, no será una gran experiencia, ¿verdad?
Entonces, ¿cómo lo respaldaremos?
Como recordatorio, así es como se ve la IU de Glasses.
Tiene un diseño único para superponer contenido sin problemas en tu campo visual.
Pero es muy simple, intentamos mantenerlo simple.
Si vemos un ejemplo aquí, puedes ver que se trata de una lista.
Se enfoca en un elemento determinado en el centro.
¿Qué queremos hacer realmente?
Bueno, queremos seleccionarlo o, tal vez, pasar al anterior o al siguiente.
No necesariamente necesitamos una ubicación en la pantalla.
Y esta es un poco más complicada, pero puedes ver que básicamente hay tres opciones: puedes colgar, activar el sonido o desactivar el video.
Pero, en realidad, quieres seleccionarlo o moverte a otro y seleccionarlo.
Por lo tanto, pensamos que no es necesario asignar directamente una ubicación en la pantalla o, en este caso, en los ojos.
Así es como se ve en realidad.
Por ejemplo, como puedes ver aquí, me muevo entre elementos en la IU de Glasses, y esto se ajusta a lo que queremos.
Puedes presionarlo.
Ahora, analizamos Android para pensar qué existe que podríamos usar para respaldar esto.
Y si alguna vez, si obtienen… Oh, lo siento.
Aquí nos estamos adelantando.
Voy a volver.
Aquí vamos.
Bien.
Por lo tanto, si alguna vez usaron uno de estos dispositivos y lo vincularon con un dispositivo Android, sabrán que pueden pasar fácilmente de los elementos de la IU de uno al otro.
Por ejemplo, con el control remoto, puedes ver que puedes Si imaginas o lo tienes vinculado con Google TV, y tienes una cuadrícula completa de películas, puedes presionar los botones de flecha para ir a la que quieras, y puedes presionar el botón para seleccionarla, el botón de aceptar.
Si tienes un menú de juego y este control, puedes usar el pad direccional o el joystick para moverte a un elemento de la IU, por ejemplo, en un menú, y seleccionarlo.
Lo mismo ocurre con el teclado.
Por lo tanto, esto coincide con lo que queremos en Compose.
Y, de nuevo, aquí están los elementos, para que puedas hacerte una idea de cómo se ve.
Y si retrocedemos un poco más, en realidad existía este tipo de panel táctil que se vinculaba con una TV y que, en realidad, usaba algo llamado navegación táctil.
Y la idea es que las coordenadas x e y no se tocan ni se vinculan a la pantalla en absoluto.
Solo se usan para la detección de gestos.
Entonces, lo que haces allí es que, si deslizas el dedo hacia la derecha, podrías imaginar que es como presionar, por ejemplo, la flecha para pasar.
Si deslizas el dedo hacia la izquierda, te mueves hacia la izquierda, arriba y abajo.
Por lo tanto, esto nos da, en su mayoría, lo que queremos.
Solo usas x e y para el seguimiento de velocidad, para determinar esos arrastres y deslizamientos, etc. Por lo tanto, eso nos da la mayor parte de lo que queremos para comenzar a usar Compose.
No es exacto porque está asignado a la TV, por lo que debemos asegurarnos de no seguir la ruta de la TV, no arruinar las TVs más adelante, por lo que debemos asegurarnos de dividirlas, pero eso es algo que veremos en un momento.
Pero nos da un punto de partida.
Para que podamos usar este tipo de navegación táctil.
Muy bien.
¿Cómo será el diseño de la API?
En Compose, la entrada más general o común es la táctil, ¿verdad?
Por lo tanto, tenemos mucho trabajo por delante.
Por lo tanto, tuvimos que averiguar cómo se relaciona con eso.
Como recordatorio, así es como se ve el PointerEvent.
Aquí puedes ver que tienes el tipo, que es el tipo de movimiento hacia abajo en el movimiento hacia arriba, y luego tienes este PointerInputChange, que es solo una lista. Puedes pensar en ella como dedos.
Si tienes tres dedos hacia abajo, tendrás tres elementos en la lista.
Por eso, queríamos que se viera muy parecido a esto.
Ahora bien, este no es un módulo de IU, que es un módulo mucho más bajo que el que usan la mayoría de los desarrolladores cuando trabajan con entradas.
Estarán una capa por encima de la capa de base.
Y ahí es donde están todos nuestros detectores de gestos.
Como desarrolladores, no les importan todos estos detalles de entrada.
Solo quieres saber si se produjo un arrastre, un clic o un deslizamiento, y luego realizas la acción en función de eso.
Por lo tanto, tenemos un montón de código que hace eso por ti, y se basa en estos de forma interna.
Entonces, empezamos a pensar: "Bien, tenemos que hacer que sea lo más fácil posible para que el código existente en Foundation pueda admitir fácilmente estos nuevos tipos".
Por lo tanto, creamos un tipo nuevo que se ve casi igual.
Solo se llama indirecta.
Y el significado indirecto es que no se trata de un toque directo en la pantalla.
Por lo tanto, te permite saber que, oye, esto se acerca. No pienses en esto desde el punto de vista táctil.
Piensa en que solo estamos controlando gestos aquí y navegando entre elementos de la IU.
El tipo es el mismo.
Tiene una lista de dedos.
En este caso, es IndirectPointerInputChange.
Así que hicimos eso y funcionó muy bien.
Pero luego encontramos un elemento más llamado eje de movimiento direccional principal.
Es un poco largo, pero tendrá sentido cuando lo explique, porque es interesante cómo surgió.
¿Cómo surgió?
Bueno, de nuevo, tienes este panel táctil en el costado de la cabeza, y es ancho y corto.
Y, en este punto, tuvimos la idea de que, bueno, tenemos la navegación táctil.
Por lo tanto, podemos deslizar el dedo hacia la izquierda y hacia la derecha.
Y eso funciona bien.
Pero si piensas en cómo existen los paneles táctiles ahora, ¿verdad?
Cuando te muevas hacia la izquierda o la derecha, lo harás en el panel táctil con el contenido de la IU.
Si vas hacia arriba o hacia abajo, te desplazarás por la lista en esa dirección.
Si intentas hacer eso con lentes, no es tan malo, ¿verdad?
Pero si haces esto, no es una gran experiencia.
Por lo tanto, descubrimos que el movimiento más natural es, obviamente, presionar, pero deslizando el dedo por el costado de los lentes.
Por lo tanto, realizamos varios estudios con usuarios, y ellos esperan que, cuando se desliza el dedo de forma horizontal, el contenido no se mueva de forma horizontal, sino vertical.
Y puedes pensar en esto si lo intentas en tu cabeza, como cuando deslizas el dedo aquí, si tienes una lista frente a ti, como avanzar, puedes avanzar por la lista o puedes retroceder por la lista.
Por lo tanto, tomamos una entrada bidimensional y tratamos de pensarla de forma unidimensional.
Y de ahí surgió la idea del eje de movimiento principal: puedes moverte horizontalmente y el objeto se moverá verticalmente.
Por lo tanto, puedes especificar en el dispositivo mismo.
Puedes decir, oye, este no es un panel táctil normal.
Quiero que la dirección principal sea horizontal, y ese es el único eje que quiero seguir.
Y de ahí es de donde vino.
Bien.
Así que nos sentimos bastante bien con eso.
Compose se encarga de todo.
Y tienes esta bonita implementación vertical.
Puedes pensar en esto.
Entonces, la forma en que lo asignamos es que ahora, con la definición horizontal, solo tenemos un elemento anterior y uno siguiente.
No tenemos arriba, abajo, izquierda, derecha.
Solo tenemos hacia adelante y hacia atrás.
Y, luego, podemos asignar eso si conoces el enfoque con el siguiente y el anterior.
Ahora, solo nos moveremos al siguiente y al anterior.
Y esto funciona muy bien para el formato vertical.
Y también es más agradable.
Funciona con el diseño horizontal de forma predeterminada, ¿verdad?
Porque solo tienes un montón de elementos enfocados.
Solo tienes que ir a la siguiente o a la anterior, y funciona con ambas.
Este es un caso con Compose en el que definimos y descubrimos todo esto en Compose, pero sería muy bueno si el dispositivo pudiera decirlo, porque sabe si tiene una forma extraña y sabe si quiere solo este eje principal, pero no tiene forma de definirlo.
Entonces, tuvimos que llevar este concepto a la plataforma y decir: "Bien, debemos definir esto en la plataforma para poder estandarizarlo y que estos dispositivos puedan especificarlo, de modo que Compose pueda admitirlo por completo".
Por lo tanto, hay un caso en el que trabajamos en Compose, avanzamos en Compose y retrocedimos, y lo trajimos con nosotros.
Así que eso es exactamente lo que hicimos.
Creamos esta nueva API para que tú o el dispositivo puedan especificarlo.
InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()Y si recuerdas, antes, cuando hablé de la entrada táctil, o de la navegación táctil, me refería a que estaba pensada principalmente para las TVs.
Bueno, no queríamos romper esas implementaciones.
Por lo tanto, agregamos otro campo para especificar este nuevo tipo de entrada de vidrio.
Bien.
Entonces, al final, ¿qué significa esto para ti?
Bueno, esta es una versión más específica de lo que obtienes como detector de gestos.
// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()De forma predeterminada, si usas Foundation, solo funcionará con los detectores de gestos.
Hicimos el trabajo por ti, así que no tienes que hacer nada.
Pero si quieres crear una app para los lentes, puedes usar nuestra biblioteca Glimmer, y luego tenemos un detector de gestos especial, que solo te da, ya sabes, para que no tengas que combinarlos, puedes usar solo este, obtener el clic, el fotograma hacia adelante y el de atrás.
Bien.
Por lo tanto, ya vimos cómo funciona la API en sí, en la plataforma y en Compose.
¿Qué hay de las pruebas?
Así es como se ve la función táctil cuando quieres realizar pruebas táctiles.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}Solo tienes que decir performTouchInput y, luego, hacer un gesto.
Hay muchos gestos diferentes y, de alguna manera, creamos todos los pequeños eventos para que se produzca el gesto.
¿Cómo funciona eso a nivel general?
Bueno, tienes el módulo de IU de Compose y el módulo de pruebas de IU, y se comunican, al menos en cuanto a la entrada, con MotionEvents.
Además, el módulo de pruebas de IU también usa un mecanismo de cola, que se basa en MotionEvent.
¿Por qué te cuento esto?
Porque no puedes crear de manera programática un MotionEvent que sea como un evento de entrada indirecto, lo que fue genial para mí porque significó que tuve que reescribir toda la comunicación entre los módulos y el sistema de colas.
Pero lo hice.
¿Qué significa esto para ti?
Si estás acostumbrado a las pruebas de entrada táctil, esto es lo que obtienes para la entrada indirecta.
Solo debes agregar indirecto al nombre.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}Ahora, pasé algunos parámetros adicionales.
Es probable que reconozcas el primero, que es donde especificas la orientación horizontal.
Y, luego, el tamaño de entrada sería, obviamente, el tamaño del panel táctil.
Esto no cambia mucho.
Si observas el código fuente, puedes ver que acabo de escribir una función de extensión y se ve así.
@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}Bien.
Eso debería darte una idea de todo el trabajo que hicimos solo para admitir los paneles táctiles en los marcos o en los lentes inteligentes.
Pero estamos haciendo estos, estoy haciendo estos ejercicios para el seguimiento de ojos y el monitoreo de manos también, así que hay mucho más por venir.
En resumen, ¿qué quiero que recuerden de mi parte?
Por lo tanto, ya sea que uses un panel táctil con un teléfono, lentes o cualquier otra entrada, pensamos en eso y tratamos de hacer el trabajo con anticipación.
Por lo tanto, si usas estos detectores de gestos de Foundation, funcionará de inmediato.
Ese es el número uno.
En segundo lugar, Compose es el kit de herramientas de IU principal, y seguiremos explorando y admitiendo nuevos tipos de entrada, tanto en la plataforma como en el kit de herramientas, que es lo que hice aquí.
Y, luego, para probar las APIs, quiero mantener una superficie y un paradigma conocidos para que sea lo más fácil posible migrar y probar estos nuevos tipos.
Gracias, espero que esta información te haya sido útil.
onNodeWithContentDescription("Photo")
.performTrackpadInput {
click(center)
pan(Offset(0f, 100f))
scale(0.9f)
}InputDevice.ViewBehavior
.getPrimaryDirectionalMotionAxis()// Glimmer Library
Box(
modifier =
Modifier.fillMaxSize()
.onIndirectPointerGesture(
enabled = true,
onSwipeForward = { /* onSwipeForward */ },
onSwipeBackward = { /* onSwipeBackward */ },
onClick = { /* onClick */ },
)
.focusTarget()
) {
// App()@Test
clickTest {
rule.onNodeWithTag(defaultTag)
.performTouchInput
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput (
IndirectPointerEventPrimaryDirectionalMotionAxis.X,
inputDeviceSize,
)
{ click(center) }
}@Test
clickTest {
rule.onNodeWithTag(defaultTag)
// .performTouchInput
.performIndirectPointerInput
{ click(center) }
}