Android: Unterschied zwischen onInterceptTouchEvent und dispatchTouchEvent?

249

Was ist der Unterschied zwischen onInterceptTouchEventund dispatchTouchEventin Android?

Laut dem Android-Entwicklerhandbuch können beide Methoden verwendet werden, um ein Berührungsereignis abzufangen ( MotionEvent), aber was ist der Unterschied?

Wie tun onInterceptTouchEvent, dispatchTouchEventund onTouchEventinteract zusammen innerhalb einer Hierarchie von Views ( ViewGroup)?

Anne Droid
quelle

Antworten:

272

Der beste Ort, um dies zu entmystifizieren, ist der Quellcode. Die Dokumente sind absolut unzureichend, um dies zu erklären.

dispatchTouchEvent ist tatsächlich in Activity, View und ViewGroup definiert. Stellen Sie sich das als einen Controller vor, der entscheidet, wie die Berührungsereignisse weitergeleitet werden.

Der einfachste Fall ist beispielsweise der von View.dispatchTouchEvent, bei dem das Berührungsereignis entweder an OnTouchListener.onTouch weitergeleitet wird, sofern es definiert ist, oder an die Erweiterungsmethode onTouchEvent .

Für ViewGroup.dispatchTouchEvent sind die Dinge viel komplizierter. Es muss herausfinden, welche seiner untergeordneten Ansichten das Ereignis erhalten soll (durch Aufrufen von child.dispatchTouchEvent). Dies ist im Grunde ein Treffer-Test-Algorithmus, bei dem Sie herausfinden, welches Begrenzungsrechteck der untergeordneten Ansicht die Berührungspunktkoordinaten enthält.

Bevor das Ereignis jedoch an die entsprechende untergeordnete Ansicht gesendet werden kann, kann der Elternteil das Ereignis ausspionieren und / oder abfangen. Dafür ist onInterceptTouchEvent da. Daher wird diese Methode zuerst aufgerufen, bevor der Treffer-Test durchgeführt wird. Wenn das Ereignis entführt wurde (indem von onInterceptTouchEvent true zurückgegeben wird), wird ACTION_CANCEL an die untergeordneten Ansichten gesendet , damit diese ihre Verarbeitung von Berührungsereignissen (von früheren Berührungsereignissen) abbrechen können Alle Berührungsereignisse auf übergeordneter Ebene werden an onTouchListener.onTouch (falls definiert) oder onTouchEvent () gesendet . Auch in diesem Fall wird onInterceptTouchEvent nie wieder aufgerufen.

Möchten Sie sogar [Activity | ViewGroup | View] .dispatchTouchEvent überschreiben? Wenn Sie kein benutzerdefiniertes Routing durchführen, sollten Sie dies wahrscheinlich nicht tun.

Die Haupterweiterungsmethoden sind ViewGroup.onInterceptTouchEvent, wenn Sie Berührungsereignisse auf übergeordneter Ebene ausspionieren und / oder abfangen möchten, und View.onTouchListener / View.onTouchEvent für die Behandlung von Hauptereignissen.

Alles in allem ist das Design imo zu kompliziert, aber Android Apis tendieren eher zur Flexibilität als zur Einfachheit.

numan salati
quelle
10
Dies ist eine großartige, prägnante Antwort. Ein ausführlicheres Beispiel finden Sie in der Schulung "Verwalten von Berührungsereignissen in einer ViewGroup"
TalkLittle
1
@numan salati: "Von da an werden alle Berührungsereignisse auf übergeordneter Ebene an onTouchListener.onTouch gesendet." - Ich möchte die Versandberührungsmethode in meiner Ansichtsgruppe überschreiben und die Ereignisse an onTouchListener senden. Aber ich sehe nicht, wie es gemacht werden kann. Es gibt keine API dafür wie View.getOnTouchListener (). OnTouch (). Es gibt eine setOnTouchListener () -Methode, aber keine getOnTouchListener () -Methode. Wie geht das dann?
Ashwin
@Ashwin meine Gedanken genau, es gibt auch kein setOnInterceptTouchEvent. Sie können eine Unterklassenansicht zur Verwendung in einem Layout überschreiben / im Code hinzufügen, aber Sie können nicht mit RootViews auf Fragment- / Aktivitätsebene herumspielen, da Sie diese Ansichten nicht unterklassifizieren können, ohne das Fragment / die Aktivität selbst zu unterklassifizieren (wie bei den meisten Kompatibilitäten) ProgressActivitiy-Implementierungen). Die API benötigt der Einfachheit halber ein setOnInterceptTouchEvent. Jeder benutzt interceptTouch irgendwann auf rootViews in einer
halbkomplexen
244

Denn dies ist das erste Ergebnis bei Google. Ich möchte mit Ihnen einen großartigen Vortrag von Dave Smith auf Youtube teilen : Beherrschen des Android Touch Systems und der Folien finden Sie hier . Es gab mir ein gutes tiefes Verständnis für das Android Touch System:

Wie die Aktivität mit Berührungen umgeht:

  • Activity.dispatchTouchEvent()
    • Immer zuerst angerufen werden
    • Sendet ein Ereignis an die an das Fenster angehängte Stammansicht
    • onTouchEvent()
      • Wird aufgerufen, wenn keine Ansichten das Ereignis verbrauchen
      • Immer zuletzt angerufen werden

Wie die Ansicht Griff berühren:

  • View.dispatchTouchEvent()
    • Sendet das Ereignis zuerst an den Listener, falls vorhanden
      • View.OnTouchListener.onTouch()
    • Wenn nicht verbraucht, verarbeitet die Berührung selbst
      • View.onTouchEvent()

Wie eine ViewGroup mit Berührungen umgeht :

  • ViewGroup.dispatchTouchEvent()
    • onInterceptTouchEvent()
      • Überprüfen Sie, ob es Kinder ersetzen sollte
      • Übergibt ACTION_CANCEL an aktives Kind
      • Wenn es einmal true zurückgibt, werden ViewGroupalle nachfolgenden Ereignisse verbraucht
    • Für jede untergeordnete Ansicht (in umgekehrter Reihenfolge wurden sie hinzugefügt)
      • Wenn Berührung relevant ist (Innenansicht), child.dispatchTouchEvent()
      • Wenn es nicht von einem Vorgänger verarbeitet wird, senden Sie es an die nächste Ansicht
    • Wenn keine Kinder das Ereignis behandeln, erhält der Listener eine Chance
      • OnTouchListener.onTouch()
    • Wenn es keinen Listener gibt oder nicht behandelt wird
      • onTouchEvent()
  • Abgefangene Ereignisse springen über den untergeordneten Schritt

Er bietet auch einen Beispielcode für benutzerdefinierte Berührungen unter github.com/devunwired/ .

Antwort: Grundsätzlich dispatchTouchEvent()wird das auf jeder ViewEbene aufgerufen , um festzustellen, ob a Viewan einer fortlaufenden Geste interessiert ist. In eines ViewGroupder ViewGrouphat die Möglichkeit , die Touch - Ereignisse in seinem zu stehlen dispatchTouchEvent()-Methode, bevor es nennen würde dispatchTouchEvent()auf den Kindern. Das ViewGroupwürde den Versand nur stoppen, wenn die ViewGroup onInterceptTouchEvent()-method true zurückgibt. Der Unterschied besteht darin, dass der dispatchTouchEvent()Versand erfolgt MotionEventsund angegeben onInterceptTouchEventwird, ob er abfangen soll (nicht MotionEventan Kinder versenden ) oder nicht (an Kinder versenden) .

Sie können sich vorstellen, dass der Code einer ViewGroup mehr oder weniger dies tut (sehr vereinfacht):

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

Ergänzende Antwort

Hier sind einige visuelle Ergänzungen zu den anderen Antworten. Meine vollständige Antwort ist hier .

Geben Sie hier die Bildbeschreibung ein

Geben Sie hier die Bildbeschreibung ein

Die dispatchTouchEvent()Methode a ViewGroupverwendet, um onInterceptTouchEvent()zu wählen, ob das Berührungsereignis (mit onTouchEvent()) sofort behandelt werden soll oder ob die dispatchTouchEvent()Methoden seiner untergeordneten Elemente weiterhin benachrichtigt werden sollen.

Suragch
quelle
Kann onInterceptTouchEvent auch in der Aktivität aufgerufen werden? Ich denke es ist nur in einer ViewGroup möglich oder irre ich mich? @ Suragch
Federico Rizzo
@ FedericoRizzo, du hast recht! Vielen Dank! Ich habe das Diagramm und meine Antwort aktualisiert.
Suragch
20

Es gibt viel Verwirrung über diese Methoden, aber es ist eigentlich nicht so kompliziert. Die meiste Verwirrung ist, weil:

  1. Wenn Ihr View/ViewGroupoder eines seiner Kinder nicht zurück wahr in onTouchEvent, dispatchTouchEventund onInterceptTouchEventwerden NUR für aufgerufen werden MotionEvent.ACTION_DOWN. Ohne ein True from onTouchEventgeht die übergeordnete Ansicht davon aus , dass Ihre Ansicht die MotionEvents nicht benötigt.
  2. Wenn keines der untergeordneten Elemente einer ViewGroup in onTouchEvent true zurückgibt, wird NUR onInterceptTouchEvent aufgerufen MotionEvent.ACTION_DOWN, selbst wenn Ihre ViewGroup in true true zurückgibt onTouchEvent.

Der Verarbeitungsauftrag lautet wie folgt:

  1. dispatchTouchEvent wird genannt.
  2. onInterceptTouchEventwird aufgerufen MotionEvent.ACTION_DOWNoder wenn eines der untergeordneten Elemente der ViewGroup true in zurückgegeben hat onTouchEvent.
  3. onTouchEventwird zuerst für die untergeordneten Elemente der ViewGroup aufgerufen, und wenn keines der untergeordneten Elemente true zurückgibt, wird es für die aufgerufen View/ViewGroup.

Wenn Sie eine Vorschau TouchEvents/MotionEventsanzeigen möchten, ohne die Ereignisse für Ihre Kinder zu deaktivieren, müssen Sie zwei Dinge tun:

  1. Überschreiben, dispatchTouchEventum eine Vorschau des Ereignisses anzuzeigen und zurückzukehren super.dispatchTouchEvent(ev).
  2. Überschreiben onTouchEventund true zurückgeben, sonst erhalten Sie keine MotionEvent außer MotionEvent.ACTION_DOWN.

Wenn Sie eine Geste wie ein Wischereignis erkennen möchten, ohne andere Ereignisse für Ihre Kinder zu deaktivieren, solange Sie die Geste nicht erkannt haben, können Sie dies folgendermaßen tun:

  1. Zeigen Sie eine Vorschau der MotionEvents wie oben beschrieben an und setzen Sie ein Flag, wenn Sie Ihre Geste erkannt haben.
  2. onInterceptTouchEventGeben Sie true zurück, wenn Ihr Flag gesetzt ist, um die MotionEvent-Verarbeitung durch Ihre Kinder abzubrechen. Dies ist auch ein praktischer Ort, um Ihre Flagge zurückzusetzen, da onInterceptTouchEvent erst beim nächsten Mal wieder aufgerufen wird MotionEvent.ACTION_DOWN.

Beispiel für Überschreibungen in a FrameLayout(mein Beispiel in ist C #, da ich mit Xamarin Android programmiere, aber die Logik in Java ist dieselbe):

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.
quelle
3
Ich weiß nicht, warum dies nicht mehr positive Stimmen hat. Es ist eine gute Antwort (IMO) und ich fand es sehr hilfreich.
Mark Ormesher
8

Auf dieser Webseite http://doandroids.com/blogs/tag/codeexample/ bin ich auf eine sehr intuitive Erklärung gestoßen . Von dort genommen:

  • boolean onTouchEvent (MotionEvent ev) - wird aufgerufen, wenn ein Berührungsereignis mit dieser Ansicht als Ziel erkannt wird
  • boolean onInterceptTouchEvent (MotionEvent ev) - wird aufgerufen, wenn ein Berührungsereignis mit dieser ViewGroup oder einem untergeordneten Element als Ziel erkannt wird. Wenn diese Funktion true zurückgibt, wird das MotionEvent abgefangen, dh es wird nicht an das untergeordnete Element, sondern an das onTouchEvent dieser Ansicht weitergegeben.
Krzysztow
quelle
2
Die Frage bezieht sich auf onInterceptTouchEvent und dispatchTouchEvent. Beide werden vor onTouchEvent aufgerufen. In diesem Beispiel wird dispatchTouchEvent jedoch nicht angezeigt.
Dayerman
8

dispatchTouchEvent-Handles vor onInterceptTouchEvent.

Anhand dieses einfachen Beispiels:

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

Sie können sehen, dass das Protokoll wie folgt aussehen wird:

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

Wenn Sie also mit diesen beiden Handlern arbeiten, verwenden Sie dispatchTouchEvent, um das Ereignis in erster Instanz zu behandeln, das an onInterceptTouchEvent gesendet wird.

Ein weiterer Unterschied besteht darin, dass wenn dispatchTouchEvent 'false' zurückgibt, das Ereignis nicht an das untergeordnete Element weitergegeben wird, in diesem Fall an EditText. Wenn Sie in onInterceptTouchEvent false zurückgeben, wird das Ereignis dennoch an EditText gesendet

Dayerman
quelle
5

Kurze Antwort: dispatchTouchEvent() wird aufgerufen, zuerst von allen.

Kurzer Rat: sollte nicht außer Kraft gesetzt werden, dispatchTouchEvent()da es schwer zu kontrollieren ist, manchmal kann es Ihre Leistung verlangsamen. IMHO, ich schlage vor, zu überschreiben onInterceptTouchEvent().


Da in den meisten Antworten das Flow-Touch-Ereignis in Aktivität / Ansichtsgruppe / Ansicht ziemlich deutlich erwähnt wird, füge ich in ViewGroup(Ignorieren dispatchTouchEvent()) nur weitere Details zum Code dieser Methoden hinzu :

onInterceptTouchEvent()wird zuerst aufgerufen, das ACTION-Ereignis wird jeweils down -> move -> up aufgerufen. Es gibt 2 Fälle:

  1. Wenn Sie in 3 Fällen false zurückgeben (ACTION_DOWN, ACTION_MOVE, ACTION_UP), wird berücksichtigt, dass der Elternteil dieses Berührungsereignis nicht benötigt , sodass onTouch()die Eltern niemals onTouch()anrufen , sondern die Kinder stattdessen anrufen . Bitte beachten Sie jedoch:

    • Sie onInterceptTouchEvent()erhalten weiterhin ein Touch-Ereignis, solange ihre Kinder nicht anrufen requestDisallowInterceptTouchEvent(true).
    • Wenn dieses Ereignis nicht von Kindern empfangen wird (dies kann in zwei Fällen vorkommen: Keine Kinder an der Position, die der Benutzer berührt, oder es gibt Kinder, aber bei ACTION_DOWN wird false zurückgegeben), senden die Eltern dieses Ereignis an onTouch()die Eltern zurück.
  2. Umgekehrt, wenn Sie true zurückgeben , stiehlt der Elternteil dieses Berührungsereignis sofort und onInterceptTouchEvent()stoppt sofort, anstatt dass onTouch()die Eltern aufgerufen werden und alle onTouch()Kinder das letzte Aktionsereignis erhalten - ACTION_CANCEL ( dh die Eltern) Touch-Ereignis gestohlen, und Kinder können von da an nicht mehr damit umgehen. Der Fluss der onInterceptTouchEvent()Rückgabe false ist normal, aber es gibt eine kleine Verwechslung mit dem Fall return true, daher liste ich ihn hier auf:

    • Geben Sie bei ACTION_DOWN true zurück. onTouch()Die Eltern erhalten erneut ACTION_DOWN und die folgenden Aktionen (ACTION_MOVE, ACTION_UP).
    • Geben Sie bei ACTION_MOVE true zurück. onTouch()Die Eltern erhalten die nächste ACTION_MOVE (nicht dieselbe ACTION_MOVE in onInterceptTouchEvent()) und die folgenden Aktionen (ACTION_MOVE, ACTION_UP).
    • Geben Sie bei ACTION_UP true zurück, da onTouch()die Eltern überhaupt NICHT aufgerufen werden, da es für die Eltern zu spät ist, ein Berührungsereignis zu stehlen.

Eine weitere wichtige Sache ist, dass ACTION_DOWN des Ereignisses in onTouch()bestimmt, ob die Ansicht mehr Aktion von diesem Ereignis erhalten möchte oder nicht. Wenn die Ansicht bei ACTION_DOWN in true zurückgibt onTouch(), bedeutet dies, dass die Ansicht bereit ist, weitere Aktionen von diesem Ereignis zu erhalten. Andernfalls bedeutet die Rückgabe von false bei ACTION_DOWN in onTouch(), dass die Ansicht von diesem Ereignis keine weitere Aktion erhält.

Nguyen Tan Dat
quelle
3

Der folgende Code in einer ViewGroup-Unterklasse verhindert, dass übergeordnete Container Berührungsereignisse empfangen:

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

Ich habe dies mit einem benutzerdefinierten Overlay verwendet, um zu verhindern, dass Hintergrundansichten auf Berührungsereignisse reagieren.

James Wald
quelle
1

Der Hauptunterschied :

• Activity.dispatchTouchEvent (MotionEvent) - Hiermit kann Ihre Aktivität alle Berührungsereignisse abfangen, bevor sie an das Fenster gesendet werden.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Hiermit kann eine ViewGroup Ereignisse überwachen, wenn sie an untergeordnete Ansichten gesendet werden.

ChapMic
quelle
1
Ja, ich kenne diese Antworten aus dem Android Dev Guide - trotzdem unklar. Die Methode dispatchTouchEvent existiert auch für eine ViewGroup, nicht nur für eine Aktivität. Meine Frage war, wie die drei Methoden dispatchTouchEvent, onInterceptTouchEvent und onTouchEvent innerhalb einer Hierarchie von ViewGroups, z. B. RelativeLayouts, zusammenwirken.
Anne Droid
1

ViewGroups onInterceptTouchEvent()ist immer der Einstiegspunkt für ein ACTION_DOWNEreignis, das als erstes Ereignis auftritt.

Wenn ViewGroup diese Geste verarbeiten soll, geben Sie true from zurück onInterceptTouchEvent(). Bei der Rückkehr wahr, Viewgroup der onTouchEvent()alle nachfolgenden Ereignisse bis zum nächsten erhalten ACTION_UPoder ACTION_CANCEL, und in den meisten Fällen die Berührungsereignisse zwischen ACTION_DOWNund ACTION_UPoder ACTION_CANCELsind ACTION_MOVE, die normalerweise als Lauf / schleudern Gesten erkannt werden.

Wenn Sie false von zurückgeben onInterceptTouchEvent(), wird die Zielansicht onTouchEvent()aufgerufen. Es wird für nachfolgende Nachrichten wiederholt, bis Sie true von zurückgeben onInterceptTouchEvent().

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

Vipsy
quelle
0

Sowohl Activity als auch View verfügen über die Methoden dispatchTouchEvent () und onTouchEvent. Die ViewGroup verfügt ebenfalls über diese Methoden, jedoch über eine andere Methode namens onInterceptTouchEvent. Der Rückgabetyp dieser Methoden ist boolesch. Sie können die Versandroute über den Rückgabewert steuern.

Der Ereignisversand in Android beginnt unter Aktivität-> Ansichtsgruppe-> Ansicht.

Ryan
quelle
0
public boolean dispatchTouchEvent(MotionEvent ev){
    boolean consume =false;
    if(onInterceptTouchEvent(ev){
        consume = onTouchEvent(ev);
    }else{
        consume = child.dispatchTouchEvent(ev);
    }
}
Mc_fool_himself
quelle
1
Könnten Sie eine Erklärung hinzufügen?
Paul Floyd
3
Während dieses Code-Snippet die Frage lösen kann, hilft das Hinzufügen einer Erklärung wirklich, die Qualität Ihres Beitrags zu verbessern. Denken Sie daran, dass Sie die Frage für Leser in Zukunft beantworten und diese Personen möglicherweise die Gründe für Ihren Codevorschlag nicht kennen.
Rosário Pereira Fernandes
-2

Kleine Antwort:

onInterceptTouchEvent steht vor setOnTouchListener.

Sagits
quelle