Jetpack Compose bajo el capó: eventos táctiles | de Andrei Belous | mayo 2022

DESCARGO DE RESPONSABILIDAD: Este artículo se escribió en septiembre de 2021 para Compose 1.0.0 y definitivamente no describe el rendimiento completo y todos los casos posibles de manejo de eventos táctiles, pero con suerte ayuda a comprender un poco cómo fluyen los eventos táctiles en Jetpack Compose. Y también habrá muchos fragmentos de código que también pertenecen a la versión 1.0.0 de Compose 🙂

Manejar eventos táctiles personalizados no siempre es fácil. En el marco de vistas de Android, tenía un esquema bastante complejo de cómo se envían los eventos a vistas específicas, cómo se interceptan y qué devolución de llamada necesita para implementar su propio oyente de eventos táctiles. Así que nuestro amado StackOverflow tiene algunas preguntas como esta: Touch Listener no funciona.Ver gestión de eventos táctiles Jetpack Compose también proporciona mecanismos para gestionar eventos táctiles personalizados. Hay mecanismos de alto nivel, como modificadores en los que se puede hacer clic o desplazarse, y en un nivel más bajo, como pointerInput o pointerFilterInterop para trabajar con buenos MotionEvents antiguos.Pero, ¿cómo el MotionEvent real del marco de trabajo de Android está disponible para la devolución de llamada de un modificador componible específico?Esta pregunta se puede dividir en dos partes.

  1. Necesitamos entender cómo y dónde los MotionEvents de Android cruzan el límite de redacción.
  2. Necesitamos entender cómo el marco maneja estos eventos táctiles y darles devoluciones de llamadas específicas.

Un mundo compuesto comienza con ambos establecerContenido Método de extensión de ComponentActivity o establecerContenido Método de un ComposeView. Miremos dentro.setContent ext método de ComponentActivity, esto significa que el marco primero intentará encontrar el ComposeView existente en la parte superior del árbol de vista si ya está configurado, si no, se creará y ejecutará uno nuevo establecerContenido Método de un ComposeView. Esto significa que ComponerVista La clase es responsable de pasar los eventos táctiles al mundo Compose. Profundicemos. La clase ComposeView extiende AbstractComposeView, ahora abrámosla y veamos si encontramos algo relacionado con MotionEvents. No, nada relacionado con el manejo táctil, no en un ComponerVista no en AbstractComposeView. Pero hay un método llamado asegúrese de Composición creada () en AbstractComposeView que se llama desde varios lugares y si miras dentro verás uno diferente establecerContenido llamada al método :), pero esta vez establecerContenido es un método de extensión de un método ViewGroup.setContent Ext de un grupo de vistas. Básicamente funciona de manera similar a ComponentActivity.setContent, verifica si ViewGroup ya tiene uno AndroidComposeView como un niño, si es así, el contenido solo se establece en la parte superior de la vista existente; de ​​lo contrario, se eliminan todas las vistas y se crean otras nuevas AndroidComposeView y agréguelo al árbol de vistas. Y finalmente si lo compruebas AndroidComposeView implementación, puede ver que ha anulado el método dispatchTouchEvent, donde tienen lugar todas las conversiones de MotionEvents al mundo de Compose. MotionEventAdapter de alguna manera intenta convertir MotionEvent en Compose PointerInputEvent y, si tiene éxito, este evento se pasa a través del límite mundial de Compose. Y para mantener el contrato con Android View, se devuelve Boolean diciéndole a AndroidView si alguien del mundo de Compose ha consumido este evento. La implementación simplificada de una ruta de evento AndroidComposeView.dispatchTouchEventMotion tiene este aspecto: actividad.dispatchTouchEvent -> ComponerVista.dispatchTouchEvent -> AndroidComposeView.dispatchTouchEvent -> componer mundoEsquema de eventos táctiles Ahora pasemos a la segunda parte de la pregunta y profundicemos en el método PointerInputEventProcessor.process. Como primer parámetro, MotionEvent se convierte en PointerInputEvent. Luego, en función del PointerEvent «sin procesar» recibido, PointerInputChangeEventProducer calcula la «diferencia» o el «cambio» entre el evento de puntero anterior y el actual (ayuda a determinar qué evento se mueve hacia arriba o hacia abajo). Operación con clases PointerInputChange. Internamente, PointerInputChangeEventProducer almacena PointerInputData anterior y, en función de eso, calcula el «cambio» en comparación con el actual. clase de cambio de entrada de puntero La parte más interesante viene determinada en función de este marco de cambio de entrada de puntero cuando el evento actual ha cambiado a un evento inactivo y luego llama a la raíz LayoutNode golpe de prueba Método con la posición del puntero y el resultado del golpe: solo una lista cambiante de PunteroInputFilters — Una interfaz que necesita el método callback.LayoutNode.hitTest onPointerEvent llama internamente a hitTest en LayoutNodeWrapper, veamos la documentación sobre hit testing.LayoutNodeWrapper es una clase abstracta y tiene PointerInputDelegatingWrapper como uno de sus descendientes. Y en eso vemos la lógica descrita en la documentación del método hitTest. Cuando el evento de entrada del puntero está dentro de los límites de la capa, agregamos PointerInputFilter a la colección de hitPointerInputFilters y seguimos llamando a la implementación de hitTest envuelto object.hitTest conmigo, el gráfico está llegando 🙂Uf… eso fue difícil, pero ahora está claro que cuando se avecina un nuevo evento de baja, el marco:

  1. Convierta Android MotionEvent a tipo interno
  2. Calcule los cambios entre el evento actual y el evento anterior y genere una instancia de la clase PointerInputChanges
  3. Cuando este evento es Nuevo Evento hacia abajo para punteros, el marco atraviesa todo el árbol de LayoutNode para encontrar PointerInputFilters que estén interesados ​​en ese evento al determinar si las coordenadas del evento del evento táctil se encuentran dentro de los límites de LayoutNode.
  4. Reúna todos estos PointerInputFilters en una lista
  5. Luego envíe esa lista a HitPathTracker.addHitPath. Esto permite futuras llamadas a HitPathTracker.dispatchChanges para enviar los PointerInputChanges correctos al PointerInputFilter correcto en el momento correcto.

Código completo del método de proceso Una pregunta final debe responderse, cómo la devolución de llamada de Modifier.pointerInput se convierte en parte de LayoutNodeWrapper Cuando se aplica un modificador a LayoutNode, la cadena de modificadores se despliega y, en función de ella, se crea una nueva cadena de LayoutNodeWrappers y basado en el LayoutNode.modifier actual -Código de cambio aplicado. Y el esquema completo del flujo de eventos táctiles se ve así:Esquema completo del flujo de eventos táctiles. Espero que hayas disfrutado leyéndolo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.