Android: ¿Diferencia entre onInterceptTouchEvent y dispatchTouchEvent?

249

¿Cuál es la diferencia entre onInterceptTouchEventy dispatchTouchEventen Android?

Según la guía para desarrolladores de Android, ambos métodos se pueden utilizar para interceptar un evento táctil ( MotionEvent), pero ¿cuál es la diferencia?

¿Cómo hacer onInterceptTouchEvent, dispatchTouchEventy onTouchEventinteractúan entre sí dentro de una jerarquía de Vistas ( ViewGroup)?

Anne Droid
fuente

Respuestas:

272

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 recibir el evento (llamando a child.dispatchTouchEvent). Esto es básicamente un algoritmo de prueba de éxito en el que descubres 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. Para eso está onInterceptTouchEvent . Por lo tanto, llama a este método primero antes de hacer la prueba de impacto y si el evento fue secuestrado (devolviendo true desde onInterceptTouchEvent) envía un ACTION_CANCEL a las vistas secundarias para que puedan abandonar su procesamiento de eventos táctiles (de eventos táctiles anteriores) y de ahí en adelante Todos los eventos táctiles en el nivel primario se envían a onTouchListener.onTouch (si está definido) o onTouchEvent (). También en ese caso, onInterceptTouchEvent nunca se llama de nuevo.

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

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

En general, su diseño excesivamente complicado, pero las API de Android se inclinan más hacia la flexibilidad que la simplicidad.

numan salati
fuente
10
Esta es una gran respuesta concisa. Para obtener un ejemplo más detallado, consulte la capacitación "Gestión de eventos táctiles en un grupo de visualización"
TalkLittle
1
@numan salati: "a partir de ese momento, todos los eventos táctiles en el nivel primario se envían a onTouchListener.onTouch". Quiero anular el método táctil de envío en mi grupo de vistas y hacer que envíe los eventos a onTouchListener. Pero no veo cómo se puede hacer. No hay una API para eso como View.getOnTouchListener (). OnTouch (). Hay un método setOnTouchListener () pero no hay un método getOnTouchListener (). ¿Cómo se puede hacer entonces?
Ashwin
@Ashwin mis pensamientos exactamente, tampoco hay setOnInterceptTouchEvent. Puede anular una vista de subclase para usarla en un diseño / agregarla en el código, pero no puede perder el tiempo con rootViews de nivel de actividad / fragmento, ya que no puede subclasificar esas vistas sin subclasificar el fragmento / actividad en sí (como en la mayoría de las compatibilidades ProgressActivitiy implementaciones). La API necesita un setOnInterceptTouchEvent para simplificar. Todo el mundo usa interceptTouch en rootViews en algún momento en una aplicación semi-compleja
leRobot
244

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

Cómo maneja la actividad el tacto:

  • Activity.dispatchTouchEvent()
    • Siempre primero en ser llamado
    • Envía el evento a la vista raíz adjunta a la ventana
    • onTouchEvent()
      • Se invoca si ninguna vista consume el evento
      • Siempre duran para ser llamados

Cómo maneja la vista táctil:

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

Cómo un ViewGroup maneja el tacto:

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Verifique si debe reemplazar a los niños
      • Pases ACTION_CANCEL para niño activo
      • Si devuelve verdadero una vez, ViewGroupconsume todos los eventos posteriores
    • Para cada vista secundaria (en orden inverso se agregaron)
      • Si el tacto es relevante (vista interior), child.dispatchTouchEvent()
      • Si no es manejado por un anterior, envíe a la siguiente vista
    • Si ningún niño maneja el evento, el oyente tiene la oportunidad
      • OnTouchListener.onTouch()
    • Si no hay oyente o no se maneja
      • onTouchEvent()
  • Los eventos interceptados saltan sobre el paso del niño

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

Respuesta: Básicamente dispatchTouchEvent()se invoca en cada Viewcapa para determinar si a Viewestá interesado en un gesto en curso. En una ViewGroupla ViewGrouptiene la capacidad de robar los eventos de toque en su dispatchTouchEvent()-method, antes de que llamaría dispatchTouchEvent()a los niños. losViewGroup solo detendría el envío si el ViewGroup onInterceptTouchEvent()método devuelve verdadero. La diferencia es que dispatchTouchEvent()está enviando MotionEventsy onInterceptTouchEventdice si debe interceptar (no enviar MotionEventa los niños) o no (enviar a los niños) .

Podrías 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);
}
seb
fuente
64

Respuesta suplementaria

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

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

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

Suragch
fuente
¿Se puede invocar onInterceptTouchEvent también en la actividad? ¿Creo que solo es posible en un ViewGroup o me equivoco? @Suragch
Federico Rizzo
@ FedericoRizzo, tienes razón! ¡Muchas gracias! Actualicé el diagrama y mi respuesta.
Suragch
20

Hay mucha confusión sobre estos métodos, pero en realidad no es tan complicado. La mayor parte de la confusión se debe a que:

  1. Si usted View/ViewGroupo alguno de sus hijos no devuelve verdadero onTouchEvent, dispatchTouchEventy onInterceptTouchEventSOLO se le pedirá MotionEvent.ACTION_DOWN. Sin un verdadero de onTouchEvent, la vista principal asumirá que su vista no necesita los MotionEvents.
  2. Cuando ninguno de los elementos secundarios de un ViewGroup devuelve true en onTouchEvent, ONInterceptTouchEvent SOLO será llamado 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 alguno de los elementos secundarios de ViewGroup devuelve true en onTouchEvent.
  3. onTouchEvent primero se llama a los hijos del ViewGroup y cuando ninguno de los hijos devuelve verdadero, se llama al View/ViewGroup .

Si quieres previsualizar TouchEvents/MotionEvents sin deshabilitar los eventos en sus hijos, debe hacer dos cosas:

  1. Anular dispatchTouchEvent para obtener una vista previa del evento y volver super.dispatchTouchEvent(ev);
  2. Anular onTouchEventy devolver verdadero, de lo contrario no obtendrá MotionEvent excepto MotionEvent.ACTION_DOWN.

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

  1. Obtenga una vista previa de los MotionEvents como se describió anteriormente y establezca una bandera cuando detecte su gesto.
  2. Devuelve verdadero onInterceptTouchEventcuando su bandera está configurada para cancelar el procesamiento de MotionEvent por parte de sus hijos. Este también es un lugar conveniente para restablecer su bandera, porque onInterceptTouchEvent no se volverá a llamar hasta la próxima MotionEvent.ACTION_DOWN.

Ejemplo de anulaciones en a FrameLayout(mi ejemplo en C # es porque estoy programando 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;
}
Marcel W
fuente
3
No sé por qué esto no tiene más votos a favor. Es una buena respuesta (IMO) y me pareció muy útil.
Mark Ormesher
8

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 objetivo
  • boolean onInterceptTouchEvent (MotionEvent ev): se llama cada vez que se detecta un evento táctil con este ViewGroup o un elemento secundario como objetivo. Si esta función devuelve verdadero, el MotionEvent será interceptado, lo que significa que no se transmitirá al hijo, sino al onTouchEvent de esta Vista.
Krzysztow
fuente
2
la pregunta es sobre onInterceptTouchEvent y dispatchTouchEvent. Ambos son llamados antes en onTouchEvent. Pero en ese ejemplo no puede ver dispatchTouchEvent.
Dayerman
8

dispatchTouchEvent maneja antes de 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);

Puede ver que el registro será así:

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

Entonces, en caso de que esté trabajando con estos 2 controladores, 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 propaga al elemento secundario, en este caso EditText, mientras que si devuelve false en onInterceptTouchEvent el evento aún se envía a EditText

Dayerman
fuente
5

Respuesta corta: dispatchTouchEvent() se llamará primero lugar.

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


Debido a que la mayoría de las respuestas mencionan claramente sobre el evento táctil 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), se considerará que el padre no necesitará este evento táctil , por lo que onTouch()los padres nunca llaman , pero onTouch()los niños llamarán en su lugar ; Sin embargo, tenga en cuenta:

    • El onInterceptTouchEvent()todavía siguen recibiendo eventos táctiles, siempre y cuando sus hijos no lo llaman requestDisallowInterceptTouchEvent(true).
    • Si no hay niños que reciben 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 ACTION_DOWN), los padres enviarán ese evento a onTouch()los padres.
  2. Viceversa, si devuelve verdadero , el padre robará este evento táctil inmediatamente y onInterceptTouchEvent()se detendrá de inmediato, en lugar onTouch()de que se llame a los padres y todos los onTouch()niños recibirán el último evento de acción: ACTION_CANCEL (por lo tanto, significa que los padres robaron evento táctil, y los niños no pueden manejarlo desde entonces). El flujo de onInterceptTouchEvent()retorno falso es normal, pero hay una pequeña confusión con el caso de retorno verdadero, así que lo enumero aquí:

    • Devuelva verdadero en ACTION_DOWN, onTouch()de los padres recibirán ACTION_DOWN nuevamente y las siguientes acciones (ACTION_MOVE, ACTION_UP).
    • Regrese verdadero en ACTION_MOVE, onTouch()de los padres recibirán el próximo ACTION_MOVE (no el mismo ACTION_MOVE en onInterceptTouchEvent()) y las siguientes acciones (ACTION_MOVE, ACTION_UP).
    • Devuelva verdadero en ACTION_UP, ya que onTouch()los padres NO llamarán en absoluto ya que es demasiado tarde para que los padres roben el evento táctil.

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

Nguyen Tan Dat
fuente
3

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;
  }

Utilicé esto con una superposición personalizada para evitar que las vistas de fondo respondan a eventos táctiles.

James Wald
fuente
1

La principal diferencia :

• Activity.dispatchTouchEvent (MotionEvent): esto permite que su actividad intercepte todos los eventos táctiles antes de que se envíen a la ventana.
• ViewGroup.onInterceptTouchEvent (MotionEvent): esto permite que un ViewGroup vea los eventos a medida que se envían a las Vistas secundarias.

ChapMic
fuente
1
Sí, sé que esto responde a la guía de desarrollo de Android, aunque no está claro. El método dispatchTouchEvent también existe para un ViewGroup, no solo para una Actividad. Mi pregunta fue cómo los tres métodos dispatchTouchEvent, onInterceptTouchEvent y onTouchEvent interactúan juntos dentro de una jerarquía de ViewGroups, por ejemplo, RelativeLayouts.
Anne Droid
1

ViewGroup's onInterceptTouchEvent()es siempre el punto de entrada para el ACTION_DOWNevento, que es el primer evento en ocurrir.

Si desea que ViewGroup procese este gesto, devuelva true desde onInterceptTouchEvent(). Al volver verdadero, ViewGroup's onTouchEvent()recibirá todos los eventos posteriores hasta el próximo ACTION_UPo ACTION_CANCEL, y en la mayoría de los casos, los eventos táctiles entre ACTION_DOWNy ACTION_UPo ACTION_CANCELson ACTION_MOVE, que normalmente se reconocerán como gestos de desplazamiento / lanzamiento.

Si devuelve falso desde onInterceptTouchEvent(), onTouchEvent()se llamará a la vista de destino . Se repetirá para los mensajes posteriores hasta que regrese verdadero de onInterceptTouchEvent().

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

vipsy
fuente
0

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

El envío de eventos en Android comienza desde Actividad-> Ver Grupo-> Ver.

Ryan
fuente
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_ mismo
fuente
1
¿Podría agregar alguna explicación?
Paul Floyd
3
Si bien este fragmento de código puede resolver la pregunta, incluir una explicación realmente ayuda a mejorar la calidad de su publicación. Recuerde que está respondiendo la pregunta para los lectores en el futuro, y que esas personas podrían no saber los motivos de su sugerencia de código.
Rosário Pereira Fernandes
-2

Pequeña respuesta:

onInterceptTouchEvent viene antes de setOnTouchListener.

sagits
fuente