Android: ¿diferencia entre onInterceptTouchEvent y dispatchTouchEvent?

¿Cuál es la diferencia entre onInterceptTouchEvent y dispatchTouchEvent en Android?

De acuerdo con la guía para desarrolladores de Android, ambos métodos se pueden usar para interceptar un evento táctil ( MotionEvent ), pero ¿cuál es la diferencia?

¿Cómo onInterceptTouchEvent , dispatchTouchEvent y onTouchEvent interactúan juntos dentro de una jerarquía de Vistas ( ViewGroup )?

El mejor lugar para desmitificar esto es el código fuente. Los documentos son lamentablemente inadecuados para explicar esto.

dispatchTouchEvent se define realmente en Activity, View y ViewGroup. Piense en ello como un controlador que decide cómo enrutar los eventos táctiles.

Por ejemplo, el caso más simple es el de View.dispatchTouchEvent que enrutará el evento táctil a OnTouchListener.onTouch si está definido o al método de extensión onTouchEvent .

Para ViewGroup.dispatchTouchEvent, las cosas son mucho más complicadas. Necesita averiguar cuál de sus vistas secundarias debería obtener el evento (llamando a child.dispatchTouchEvent). Básicamente, este es un algoritmo de prueba de impacto en el que se determina qué rectángulo delimitador de la vista secundaria contiene las coordenadas del punto de contacto.

Pero antes de que pueda enviar el evento a la vista secundaria apropiada, el padre puede espiar y / o interceptar el evento todos juntos. Esto es en lo que onInterceptTouchEvent está ahí. Así que llama a este método primero antes de realizar la prueba de impacto y si el evento fue secuestrado (al devolver verdadero de onInterceptTouchEvent) envía un ACTION_CANCEL a las vistas secundarias para que puedan abandonar el procesamiento de eventos táctiles (de eventos táctiles previos) y de ahí en adelante todos los eventos táctiles en el nivel principal se envían a onTouchListener.onTouch (si está definido) o onTouchEvent (). También en ese caso, enInterceptTouchEvent nunca se vuelve a llamar.

¿Te gustaría anular [Activity | ViewGroup | View] .dispatchTouchEvent? A menos que esté haciendo un enrutamiento personalizado, probablemente no debería.

Los métodos de extensión principales son ViewGroup.onInterceptTouchEvent si desea espiar y / o interceptar el evento táctil en el nivel principal y View.onTouchListener / View.onTouchEvent para el manejo de eventos principales.

Con todo, su diseño excesivamente complicado, pero las aplicaciones de Android se inclinan más por la flexibilidad que por la simplicidad.

Porque este es el primer resultado en Google. Quiero compartir con ustedes una excelente charla de Dave Smith en Youtube: el dominio del sistema Android Touch y las diapositivas están disponibles aquí . Me dio una buena comprensión profunda sobre el Android Touch System:

Cómo se maneja la actividad táctil:

  • Activity.dispatchTouchEvent()
    • Siempre primero en ser llamado
    • Envía evento a la vista raíz adjunta a la ventana
    • onTouchEvent()
      • Llamado si no hay vistas consumen el evento
      • Siempre dura para ser llamado

Cómo se maneja la vista táctil:

  • View.dispatchTouchEvent()
    • Envía evento al oyente primero, si existe
      • View.OnTouchListener.onTouch()
    • Si no se consume, procesa el toque en sí mismo
      • View.onTouchEvent()

Cómo se maneja un ViewGroup touch:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Verifica si debería reemplazar a los niños
      • Pasa ACTION_CANCEL a hijo activo
      • Devuelve verdadero una vez, consume todos los eventos posteriores
    • Para cada vista secundaria, en orden inverso se agregaron
      • Si el tacto es relevante (vista interior), child.dispatchTouchEvent()
      • Si no se manejó por previo, envíelo a la siguiente vista
    • Si ningún niño maneja un evento, el oyente tiene una oportunidad
      • OnTouchListener.onTouch()
    • Si no escucha, o no se maneja
      • onTouchEvent()
  • Los eventos interceptados saltan el paso del niño

También proporciona un código de ejemplo de toque personalizado en github.com/devunwired/ .

Respuesta: Básicamente se llama al dispatchTouchEvent() en cada capa de View para determinar si una View está interesada en un gesto en curso. En un ViewGroup ViewGroup tiene la capacidad de robar los eventos táctiles en su dispatchTouchEvent() -method, antes de llamar a dispatchTouchEvent() en los elementos secundarios. The ViewGroup solo detendría el envío si ViewGroup onInterceptTouchEvent() –method devuelve true. La diferencia es que dispatchTouchEvent() está distribuyendo MotionEvents y onInterceptTouchEvent indica si debe interceptar (no distribuir MotionEvent a los niños) o no (enviar a los niños) .

Se podría imaginar el código de un ViewGroup haciendo más o menos esto (muy simplificado):

 public boolean dispatchTouchEvent(MotionEvent ev) { if(!onInterceptTouchEvent()){ for(View child : children){ if(child.dispatchTouchEvent(ev)) return true; } } return super.dispatchTouchEvent(ev); } 

Respuesta suplementaria

Aquí hay algunos suplementos visuales para las otras respuestas. Mi respuesta completa está aquí .

enter image description here

enter image description here

El método dispatchTouchEvent() de un ViewGroup usa onInterceptTouchEvent() para elegir si debe manejar inmediatamente el evento touch (con onTouchEvent() ) o continuar notificando los métodos dispatchTouchEvent() de sus hijos.

Hay mucha confusión acerca de estos métodos, pero en realidad no es tan complicado. La mayor parte de la confusión es porque:

  1. Si su View/ViewGroup o cualquiera de sus hijos no devuelve true en onTouchEvent , dispatchTouchEvent y onInterceptTouchEvent SÓLO serán llamados para MotionEvent.ACTION_DOWN . Sin un verdadero de onTouchEvent , la vista padre supondrá que su vista no necesita MotionEvents.
  2. Cuando ninguno de los hijos de un ViewGroup devuelve true en onTouchEvent, onInterceptTouchEvent SÓLO será llamado para MotionEvent.ACTION_DOWN , incluso si su ViewGroup devuelve true en onTouchEvent .

La orden de procesamiento es así:

  1. dispatchTouchEvent se llama.
  2. onInterceptTouchEvent se llama para MotionEvent.ACTION_DOWN o cuando cualquiera de los elementos secundarios de ViewGroup devuelve true en onTouchEvent .
  3. onTouchEvent se onTouchEvent primero en los elementos secundarios del grupo de visualización y, cuando ninguno de los elementos secundarios devuelve verdadero, se View/ViewGroup en la View/ViewGroup .

Si desea obtener TouchEvents/MotionEvents vista previa de TouchEvents/MotionEvents sin deshabilitar los eventos en sus hijos, debe hacer dos cosas:

  1. Reemplazar dispatchTouchEvent para obtener una vista previa del evento y devolver super.dispatchTouchEvent(ev) ;
  2. Anular onTouchEvent y return true; de ​​lo contrario, no obtendrá ningún MotionEvent excepto MotionEvent.ACTION_DOWN .

Si desea detectar algún gesto como un evento de deslizamiento, sin desactivar otros eventos en sus hijos, siempre y cuando no haya detectado el gesto, puede hacerlo así:

  1. Obtenga una vista previa de MotionEvents como se describe arriba y establezca un indicador cuando haya detectado su gesto.
  2. Devuelve verdadero en onInterceptTouchEvent cuando tu bandera está configurada para cancelar el procesamiento de MotionEvent por tus hijos. Este también es un lugar conveniente para reiniciar su indicador, ya que onInterceptTouchEvent no se volverá a llamar hasta el próximo MotionEvent.ACTION_DOWN .

Ejemplo de reemplazos en FrameLayout (mi ejemplo es C # ya que estoy progtwigndo con Xamarin Android, pero la lógica es la misma en Java):

 public override bool DispatchTouchEvent(MotionEvent e) { // Preview the touch event to detect a swipe: switch (e.ActionMasked) { case MotionEventActions.Down: _processingSwipe = false; _touchStartPosition = e.RawX; break; case MotionEventActions.Move: if (!_processingSwipe) { float move = e.RawX - _touchStartPosition; if (move >= _swipeSize) { _processingSwipe = true; _cancelChildren = true; ProcessSwipe(); } } break; } return base.DispatchTouchEvent(e); } public override bool OnTouchEvent(MotionEvent e) { // To make sure to receive touch events, tell parent we are handling them: return true; } public override bool OnInterceptTouchEvent(MotionEvent e) { // Cancel all children when processing a swipe: if (_cancelChildren) { // Reset cancel flag here, as OnInterceptTouchEvent won't be called until the next MotionEventActions.Down: _cancelChildren = false; return true; } return false; } 

Encontré una explicación muy intuitiva en esta página web http://doandroids.com/blogs/tag/codeexample/ . Tomado de allí:

  • boolean onTouchEvent (MotionEvent ev): se llama cada vez que se detecta un evento táctil con esta vista como objective
  • boolean onInterceptTouchEvent (MotionEvent ev): se llama cada vez que se detecta un evento táctil con este ViewGroup o un elemento secundario del mismo como destino. Si esta función devuelve verdadero, se interceptará el evento MotionEvent, lo que significa que no se transmitirá al niño, sino al evento onTouchEvent de esta vista.

dispatchTouchEvent maneja antes onInterceptTouchEvent.

Usando este simple ejemplo:

  main = new LinearLayout(this){ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { System.out.println("Event - onInterceptTouchEvent"); return super.onInterceptTouchEvent(ev); //return false; //event get propagated } @Override public boolean dispatchTouchEvent(MotionEvent ev) { System.out.println("Event - dispatchTouchEvent"); return super.dispatchTouchEvent(ev); //return false; //event DONT get propagated } }; main.setBackgroundColor(Color.GRAY); main.setLayoutParams(new LinearLayout.LayoutParams(320,480)); viewA = new EditText(this); viewA.setBackgroundColor(Color.YELLOW); viewA.setTextColor(Color.BLACK); viewA.setTextSize(16); viewA.setLayoutParams(new LinearLayout.LayoutParams(320,80)); main.addView(viewA); setContentView(main); 

Puedes ver que el registro será como:

 I/System.out(25900): Event - dispatchTouchEvent I/System.out(25900): Event - onInterceptTouchEvent 

Entonces, en caso de que esté trabajando con estos 2 manejadores use dispatchTouchEvent para manejar en primera instancia el evento, que irá a onInterceptTouchEvent.

Otra diferencia es que si dispatchTouchEvent devuelve ‘false’, el evento no se propagará al elemento secundario, en este caso, EditText, mientras que si devuelve falso en onInterceptTouchEvent, el evento se enviará a EditText.

Puede encontrar la respuesta en este video https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 y los próximos 3 videos. Todos los eventos táctiles se explican muy bien, es muy claro y está lleno de ejemplos.

El siguiente código dentro de una subclase de ViewGroup evitaría que sus contenedores principales reciban eventos táctiles:

  @Override public boolean dispatchTouchEvent(MotionEvent ev) { // Normal event dispatch to this container's children, ignore the return value super.dispatchTouchEvent(ev); // Always consume the event so it is not dispatched further up the chain return true; } 

Lo usé con una superposición personalizada para evitar que las vistas en segundo plano respondan a eventos táctiles.

La principal diferencia :

• Activity.dispatchTouchEvent (MotionEvent): permite que su actividad intercepte todos los eventos táctiles antes de enviarlos a la ventana.
• ViewGroup.onInterceptTouchEvent (MotionEvent): permite a un grupo de visualización ver eventos a medida que se envían a las vistas secundarias.

ViewGroup onInterceptTouchEvent() es siempre el punto de entrada para el evento ACTION_DOWN , que es el primer evento que se produce.

Si desea que ViewGroup procese este gesto, devuelva true desde onInterceptTouchEvent() . Al regresar verdadero, el evento onTouchEvent() de onTouchEvent() recibirá todos los eventos subsiguientes hasta el próximo ACTION_UP o ACTION_CANCEL , y en la mayoría de los casos, los eventos táctiles entre ACTION_DOWN y ACTION_UP o ACTION_CANCEL son ACTION_MOVE , que normalmente se reconocerán como gestos de desplazamiento / fling.

Si devuelve false desde onInterceptTouchEvent() , se onTouchEvent() la vista de destino onTouchEvent() . Se repetirá para mensajes subsiguientes hasta que devuelva verdadero desde onInterceptTouchEvent() .

Fuente: http://neevek.net/posts/2013/10/13/implementing-onInterceptTouchEvent-and-onTouchEvent-for-ViewGroup.html

Respuesta corta: dispatchTouchEvent() se llamará en primer lugar.

Consejo breve: no debe reemplazar a dispatchTouchEvent() ya que es difícil de controlar, a veces puede ralentizar su rendimiento. En mi humilde opinión, sugiero anular onInterceptTouchEvent() .

Debido a que la mayoría de las respuestas mencionan bastante claramente el evento de toque de flujo en la actividad / vista grupo / vista, solo agrego más detalles sobre el código en estos métodos en ViewGroup (ignorando dispatchTouchEvent() ):

onInterceptTouchEvent() se llamará primero, el evento ACTION se llamará, respectivamente, hacia abajo -> mover -> hacia arriba. Hay 2 casos:

  1. Si devuelve falso en 3 casos (ACTION_DOWN, ACTION_MOVE, ACTION_UP), considerará que el padre no necesitará este evento táctil , por lo que onTouch() de los padres nunca llama , pero onTouch() de los hijos llamará en su lugar ; Sin embargo, tenga en cuenta:

    • El onInterceptTouchEvent() aún continúa recibiendo evento táctil, siempre y cuando sus hijos no invoquen requestDisallowInterceptTouchEvent(true) .
    • Si no hay niños que reciban ese evento (puede suceder en 2 casos: no hay niños en la posición que tocan los usuarios, o hay niños pero devuelve falso en ACCIÓN_DOWN), los padres enviarán ese evento de nuevo a onTouch() del padres
  2. A la inversa, si devuelve true , el padre robará este evento táctil inmediatamente y onInterceptTouchEvent() se detendrá inmediatamente, en onTouch() lugar se onTouch() de los padres y todos los onTouch() recibirán el último evento de acción. – ACTION_CANCEL (por lo tanto, significa que los padres robaron el evento táctil, y los niños no pueden manejarlo a partir de ese momento). El flujo de onInterceptTouchEvent() return false es normal, pero existe una pequeña confusión con return true case, por lo que lo onInterceptTouchEvent() aquí:

    • Devuelve true en ACTION_DOWN, onTouch() de los padres recibirán ACTION_DOWN nuevamente y las siguientes acciones (ACTION_MOVE, ACTION_UP).
    • Devuelve true en ACTION_MOVE, onTouch() de los padres recibirán el siguiente ACTION_MOVE (no el mismo ACTION_MOVE en onInterceptTouchEvent() ) y las siguientes acciones (ACTION_MOVE, ACTION_UP).
    • Devuelve true en ACTION_UP, onTouch() de los padres NO se llamará en absoluto ya que es demasiado tarde para que los padres roben el evento táctil.

Una cosa más importante es ACTION_DOWN del evento en onTouch() determinará si la vista desea recibir más acción de ese evento o no. Si la vista devuelve verdadero en ACTION_DOWN en onTouch() , significa que la vista está dispuesta a recibir más acción de ese evento. De lo contrario, devolver false en ACTION_DOWN en onTouch() implicará que la vista no recibirá más acción de ese evento.

Tanto la actividad como la vista tienen el método dispatchTouchEvent () y onTouchEvent. The ViewGroup también tienen estos métodos, pero tienen otro método llamado onInterceptTouchEvent. El tipo de devolución de esos métodos es booleano, puede controlar la ruta de envío a través del valor de retorno.

El envío del evento en Android comienza desde Activity-> ViewGroup-> View.

 public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume =false; if(onInterceptTouchEvent(ev){ consume = onTouchEvent(ev); }else{ consume = child.dispatchTouchEvent(ev); } } 

Pequeña respuesta:

onInterceptTouchEvent viene antes de setOnTouchListener.