Android: differenza tra onInterceptTouchEvent e dispatchTouchEvent?


249

Qual è la differenza tra onInterceptTouchEvente dispatchTouchEventin Android?

Secondo la guida per sviluppatori Android, entrambi i metodi possono essere utilizzati per intercettare un evento touch ( MotionEvent), ma qual è la differenza?

Come fare onInterceptTouchEvent, dispatchTouchEvente onTouchEventinteragiscono insieme all'interno di una gerarchia di Vista ( ViewGroup)?

Risposte:


272

Il posto migliore per demistificare questo è il codice sorgente. I documenti sono dolorosamente inadeguati a spiegarlo.

dispatchTouchEvent è attualmente definito su Activity, View e ViewGroup. Pensalo come un controller che decide come indirizzare gli eventi touch.

Ad esempio, il caso più semplice è quello di View.dispatchTouchEvent che indirizzerà l'evento touch a OnTouchListener.onTouch se è definito o al metodo di estensione onTouchEvent .

Per ViewGroup.dispatchTouchEvent le cose sono molto più complicate. Deve capire quale delle sue viste figlio dovrebbe ottenere l'evento (chiamando child.dispatchTouchEvent). Questo è fondamentalmente un algoritmo di hit testing in cui capisci quale rettangolo di delimitazione della vista figlio contiene le coordinate del punto di contatto.

Ma prima che possa inviare l'evento alla vista figlio appropriata, il genitore può spiare e / o intercettare l'evento tutti insieme. Ecco a cosa serve OnInterceptTouchEvent . Quindi chiama questo metodo prima di eseguire l'hit test e se l'evento è stato dirottato (restituendo true da onInterceptTouchEvent) invia un ACTION_CANCEL alle viste figlio in modo che possano abbandonare l'elaborazione degli eventi di tocco (dai precedenti eventi di tocco) e da allora in poi tutti gli eventi touch a livello padre vengono inviati a onTouchListener.onTouch (se definito) o onTouchEvent (). Anche in quel caso, onInterceptTouchEvent non viene mai più richiamato.

Vorresti addirittura sostituire [Activity | ViewGroup | View] .dispatchTouchEvent? A meno che tu non stia eseguendo un routing personalizzato, probabilmente non dovresti.

I metodi di estensione principali sono ViewGroup.onInterceptTouchEvent se si desidera spiare e / o intercettare l'evento touch a livello padre e View.onTouchListener / View.onTouchEvent per la gestione dell'evento principale.

Tutto sommato, il suo design è troppo complicato, ma le API Android vanno più alla flessibilità che alla semplicità.


10
Questa è un'ottima risposta concisa. Per un esempio più dettagliato, vedere la formazione "Gestione degli eventi tattili in un gruppo di visualizzazione"
TalkLittle

1
@numan salati: "da quel momento in poi tutti gli eventi touch a livello di padre vengono inviati a onTouchListener.onTouch" - Voglio sovrascrivere il metodo touch di spedizione nel mio gruppo di visualizzazione e fare in modo che gli eventi vengano inviati a onTouchListener. Ma non vedo come si possa fare. Non c'è API per quello come View.getOnTouchListener (). OnTouch (). Esiste un metodo setOnTouchListener () ma nessun metodo getOnTouchListener (). Come si può fare allora?
Ashwin,

@Ashwin esattamente i miei pensieri, non c'è neanche setOnInterceptTouchEvent. Puoi sovrascrivere una vista di una sottoclasse da utilizzare in un layout / aggiungerla nel codice, ma non puoi scherzare con le viste root a livello di frammento / attività, poiché non puoi sottoclassare quelle viste senza sottoclassare il frammento / attività stessa (come nella maggior parte della compatibilità Implementazioni ProgressActivitiy). L'API ha bisogno di un setOnInterceptTouchEvent per semplicità. Tutti usano interceptTouch su rootViews a un certo punto in un'app semi-complessa
leRobot

244

Perché questo è il primo risultato su Google. Voglio condividere con te un ottimo discorso di Dave Smith su Youtube: padroneggiare il sistema touch Android e le diapositive sono disponibili qui . Mi ha dato una buona conoscenza approfondita del sistema touch Android:

In che modo viene gestita l' attività :

  • Activity.dispatchTouchEvent()
    • Sempre il primo ad essere chiamato
    • Invia l'evento alla vista radice allegata a Window
    • onTouchEvent()
      • Chiamato se nessuna vista consuma l'evento
      • Sempre l'ultimo ad essere chiamato

Come gli View maniglie toccano:

  • View.dispatchTouchEvent()
    • Invia prima l'evento all'ascoltatore, se esiste
      • View.OnTouchListener.onTouch()
    • Se non consumato, elabora il tocco stesso
      • View.onTouchEvent()

Come gestisce un tocco ViewGroup :

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Controlla se deve sostituire i bambini
      • Passa ACTION_CANCEL al bambino attivo
      • Se ritorna vero una volta, ViewGroupconsuma tutti gli eventi successivi
    • Per ogni vista figlio (in ordine inverso sono stati aggiunti)
      • Se il tocco è rilevante (vista interna), child.dispatchTouchEvent()
      • Se non è gestito da un precedente, invia alla vista successiva
    • Se nessun bambino gestisce l'evento, l'ascoltatore ha una possibilità
      • OnTouchListener.onTouch()
    • Se non è presente alcun ascoltatore o non è gestito
      • onTouchEvent()
  • Gli eventi intercettati saltano sul passaggio secondario

Fornisce anche un esempio di codice di tocco personalizzato su github.com/devunwired/ .

Risposta: Fondamentalmente dispatchTouchEvent()viene chiamato su ogni Viewlivello per determinare se a Viewè interessato a un gesto in corso. In una ViewGroupla ViewGroupha la capacità di rubare gli eventi tocco nella sua dispatchTouchEvent()-Metodo, prima che avrebbe chiamato dispatchTouchEvent()i bambini. L' ViewGroupfermerebbe solo l'invio se i ViewGroup onInterceptTouchEvent()rendimenti -Metodo vero. La differenza è che dispatchTouchEvent()sta spedendo MotionEventse onInterceptTouchEventdice se dovrebbe intercettare (non spedire MotionEventai bambini) o no (spedire ai bambini) .

Potresti immaginare il codice di un ViewGroup che fa più o meno questo (molto semplificato):

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

64

Risposta supplementare

Ecco alcuni supplementi visivi alle altre risposte. La mia risposta completa è qui .

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Il dispatchTouchEvent()metodo di un ViewGrouputilizza onInterceptTouchEvent()per scegliere se gestire immediatamente l'evento touch (con onTouchEvent()) o continuare a notificare i dispatchTouchEvent()metodi dei suoi figli.


OnInterceptTouchEvent può essere chiamato anche nell'attività ?? Penso che sia possibile solo in un ViewGroup o sbaglio? @Suragch
Federico Rizzo

@FedericoRizzo, hai ragione! Grazie mille! Ho aggiornato il diagramma e la mia risposta.
Suragch,

20

C'è molta confusione su questi metodi, ma in realtà non è così complicato. Gran parte della confusione è dovuta a:

  1. Se la vostra View/ViewGroupo uno dei suoi figli non tornano vero in onTouchEvent, dispatchTouchEvente onInterceptTouchEventsarà solo chiamato per MotionEvent.ACTION_DOWN. Senza un vero da onTouchEvent, la vista padre supporrà che la vista non abbia bisogno di MotionEvents.
  2. Quando nessuno dei figli di un ViewGroup restituisce true in onTouchEvent, verrà richiesto SOLO onInterceptTouchEvent MotionEvent.ACTION_DOWN, anche se ViewGroup restituisce true in onTouchEvent.

L'ordine di elaborazione è così:

  1. dispatchTouchEvent è chiamato.
  2. onInterceptTouchEventviene richiesto MotionEvent.ACTION_DOWNo quando uno qualsiasi dei figli del ViewGroup è tornato true in onTouchEvent.
  3. onTouchEventviene prima chiamato sui figli del ViewGroup e quando nessuno dei figli ritorna vero viene chiamato sul View/ViewGroup.

Se vuoi visualizzare l'anteprima TouchEvents/MotionEventssenza disabilitare gli eventi sui tuoi figli, devi fare due cose:

  1. Sostituisci dispatchTouchEventper visualizzare in anteprima l'evento e tornare super.dispatchTouchEvent(ev);
  2. Sostituisci onTouchEvente ritorna vero, altrimenti non ne otterrai nessuno MotionEvent tranne MotionEvent.ACTION_DOWN.

Se vuoi rilevare alcuni gesti come un evento di scorrimento, senza disabilitare altri eventi sui tuoi figli finché non rilevi il gesto, puoi farlo in questo modo:

  1. Visualizza l'anteprima di MotionEvents come descritto sopra e imposta un flag quando hai rilevato il tuo gesto.
  2. Ritorna true onInterceptTouchEventquando la tua bandiera è impostata per annullare l'elaborazione MotionEvent da parte dei tuoi figli. Questo è anche un posto comodo per resettare il tuo flag, perché onInterceptTouchEvent non verrà chiamato di nuovo fino al prossimo MotionEvent.ACTION_DOWN.

Esempio di override in a FrameLayout(il mio esempio è C # mentre sto programmando con Xamarin Android, ma la logica è la stessa in 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;
}

3
Non so perché questo non abbia più voti. È una buona risposta (IMO) e l'ho trovata molto utile.
Mark Ormesher,

8

Mi sono imbattuto in una spiegazione molto intuitiva in questa pagina web http://doandroids.com/blogs/tag/codeexample/ . Tratto da lì:

  • booleano onTouchEvent (MotionEvent ev): chiamato ogni volta che viene rilevato un evento touch con questa vista come target
  • booleano onInterceptTouchEvent (MotionEvent ev): chiamato ogni volta che viene rilevato un evento touch con questo gruppo di visualizzazione o un suo figlio come destinazione. Se questa funzione restituisce true, MotionEvent verrà intercettato, il che significa che non verrà passato al figlio, ma piuttosto all'onTouchEvent di questa vista.

2
la domanda riguarda onInterceptTouchEvent e dispatchTouchEvent. Entrambi vengono chiamati prima di onTouchEvent. Ma in questo esempio non puoi vedere dispatchTouchEvent.
Dayerman,

8

dispatchTouchEvent gestisce prima di onInterceptTouchEvent.

Utilizzando questo semplice esempio:

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

Puoi vedere che il registro sarà simile a:

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

Quindi, nel caso in cui lavori con questi 2 gestori, usa dispatchTouchEvent per gestire in prima istanza l'evento, che andrà su onInterceptTouchEvent.

Un'altra differenza è che se dispatchTouchEvent restituisce 'false' l'evento non viene propagato al figlio, in questo caso EditText, mentre se si restituisce false in onInterceptTouchEvent l'evento continua a essere inviato a EditText


5

Risposta breve: dispatchTouchEvent() verrà chiamato prima di tutto.

Breve consiglio: non deve essere ignorato dispatchTouchEvent()poiché è difficile da controllare, a volte può rallentare le prestazioni. IMHO, suggerisco di ignorare onInterceptTouchEvent().


Poiché la maggior parte delle risposte menziona abbastanza chiaramente l'evento di flusso di tocco sull'attività / vista gruppo / vista, aggiungo solo maggiori dettagli sul codice su questi metodi in ViewGroup(ignorando dispatchTouchEvent()):

onInterceptTouchEvent()verrà chiamato per primo, l'evento ACTION verrà chiamato rispettivamente in basso -> sposta -> in alto. Ci sono 2 casi:

  1. Se restituisci false in 3 casi (ACTION_DOWN, ACTION_MOVE, ACTION_UP), verrà considerato come il genitore non avrà bisogno di questo evento touch , quindi onTouch()i genitori non chiamano mai , ma onTouch()dei bambini chiameranno invece ; tuttavia si prega di notare:

    • L' onInterceptTouchEvent()ancora continuano a ricevere tocco evento, a patto che i suoi figli non chiamano requestDisallowInterceptTouchEvent(true).
    • Se non ci sono bambini che ricevono quell'evento (ciò può accadere in 2 casi: nessun bambino nella posizione toccata dagli utenti o ci sono bambini ma restituisce falso in ACTION_DOWN), i genitori rispediranno l'evento a quello onTouch()dei genitori.
  2. Viceversa, se ritorni vero , il genitore ruberà immediatamente questo evento tattile e onInterceptTouchEvent()si fermerà immediatamente, anziché onTouch()i genitori verranno chiamati e tutti i onTouch()bambini riceveranno l'ultimo evento d'azione - ACTION_CANCEL (quindi, significa che i genitori ha rubato l'evento touch e i bambini non possono gestirlo da quel momento in poi). Il flusso di onInterceptTouchEvent()return false è normale, ma c'è un po 'di confusione con return true case, quindi lo elenco qui:

    • Ritorna vero su ACTION_DOWN, onTouch()dei genitori riceveranno di nuovo ACTION_DOWN e seguiranno le azioni (ACTION_MOVE, ACTION_UP).
    • Ritorna vero su ACTION_MOVE, onTouch()dei genitori riceveranno il prossimo ACTION_MOVE (non lo stesso ACTION_MOVE in onInterceptTouchEvent()) e le seguenti azioni (ACTION_MOVE, ACTION_UP).
    • Ritorna vero su ACTION_UP, onTouch()i genitori NON chiameranno affatto poiché è troppo tardi perché i genitori rubino l'evento touch.

Un'altra cosa importante è che ACTION_DOWN dell'evento onTouch()determinerà se la vista desidera ricevere più azioni da quell'evento o meno. Se la vista ritorna vera su ACTION_DOWN in onTouch(), significa che la vista è disposta a ricevere più azioni da quell'evento. Altrimenti, restituire false in ACTION_DOWN onTouch()implica che la vista non riceverà più alcuna azione da quell'evento.



3

Il seguente codice all'interno di una sottoclasse ViewGroup impedirebbe ai suoi contenitori padre di ricevere eventi touch:

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

L'ho usato con un overlay personalizzato per impedire alle visualizzazioni in background di rispondere agli eventi touch.


1

La differenza principale :

• Activity.dispatchTouchEvent (MotionEvent): consente all'attività di intercettare tutti gli eventi di tocco prima che vengano inviati alla finestra.
• ViewGroup.onInterceptTouchEvent (MotionEvent): consente a ViewGroup di guardare gli eventi mentre vengono inviati alle viste figlio.


1
Sì, conosco queste risposte dalla guida per sviluppatori Android - tuttavia non è chiaro. Il metodo dispatchTouchEvent esiste anche per un ViewGroup non solo per un'attività. La mia domanda era: come i tre metodi dispatchTouchEvent, onInterceptTouchEvent e onTouchEvent interagiscono insieme all'interno di una gerarchia di ViewGroups, ad esempio RelativeLayouts.
Anne Droid,

1

ViewGroup onInterceptTouchEvent()è sempre il punto di ingresso per l' ACTION_DOWNevento che è il primo evento che si verifica.

Se si desidera che ViewGroup elabori questo gesto, restituire true da onInterceptTouchEvent(). Al ritorno vero, ViewGroup onTouchEvent()riceverà tutti gli eventi successivi fino al prossimo ACTION_UPo ACTION_CANCEL, e nella maggior parte dei casi, gli eventi di tocco tra ACTION_DOWNe ACTION_UPo ACTION_CANCELsono ACTION_MOVE, che saranno normalmente riconosciuti come gesti di scorrimento / lancio.

Se si restituisce false da onInterceptTouchEvent(), onTouchEvent()verrà chiamata la vista di destinazione . Verrà ripetuto per i messaggi successivi fino a quando non si restituisce true da onInterceptTouchEvent().

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


0

Sia Activity che View hanno il metodo dispatchTouchEvent () e onTouchEvent. Il ViewGroup ha anche questi metodi, ma ha un altro metodo chiamato onInterceptTouchEvent. Il tipo restituito di questi metodi è booleano, è possibile controllare il percorso di spedizione attraverso il valore restituito.

L'invio di eventi in Android inizia da Attività-> Visualizza gruppo-> Visualizza.


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

1
Potresti aggiungere qualche spiegazione?
Paul Floyd,

3
Sebbene questo frammento di codice possa risolvere la domanda, inclusa una spiegazione aiuta davvero a migliorare la qualità del tuo post. Ricorda che stai rispondendo alla domanda per i lettori in futuro e quelle persone potrebbero non conoscere i motivi del tuo suggerimento sul codice.
Rosário Pereira Fernandes,

-2

Piccola risposta:

onInterceptTouchEvent viene prima di setOnTouchListener.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.