¿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); }
Aquí hay algunos suplementos visuales para las otras respuestas. Mi respuesta completa está aquí .
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:
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. MotionEvent.ACTION_DOWN
, incluso si su ViewGroup devuelve true en onTouchEvent
. La orden de procesamiento es así:
dispatchTouchEvent
se llama. onInterceptTouchEvent
se llama para MotionEvent.ACTION_DOWN
o cuando cualquiera de los elementos secundarios de ViewGroup devuelve true en onTouchEvent
. 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:
dispatchTouchEvent
para obtener una vista previa del evento y devolver super.dispatchTouchEvent(ev)
; 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í:
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()
.
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:
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:
onInterceptTouchEvent()
aún continúa recibiendo evento táctil, siempre y cuando sus hijos no invoquen requestDisallowInterceptTouchEvent(true)
. onTouch()
del padres 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í:
onTouch()
de los padres recibirán ACTION_DOWN nuevamente y las siguientes acciones (ACTION_MOVE, ACTION_UP). 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). 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.