Was ist der Unterschied zwischen onInterceptTouchEvent
und dispatchTouchEvent
in Android?
Laut Android-Entwicklerhandbuch können beide Methoden zum Abfangen eines Berührungsereignisses (MotionEvent
) verwendet werden. Aber was ist der Unterschied?
Wie interagieren onInterceptTouchEvent
, dispatchTouchEvent
und onTouchEvent
innerhalb einer Hierarchie von Views (ViewGroup
) zusammen?
Der beste Ort, um dies zu entmystifizieren, ist der Quellcode. Die Dokumente sind kläglich genug, um dies zu erklären.
dispatchTouchEvent ist eigentlich in Activity, View und ViewGroup definiert. Betrachten Sie es als Controller, der entscheidet, wie die Berührungsereignisse geleitet werden.
Der einfachste Fall ist beispielsweise der von View.dispatchTouchEvent , der das Berührungsereignis entweder zu OnTouchListener.onTouch leitet, wenn es definiert ist, oder zur Erweiterungsmethode onTouchEvent .
Für ViewGroup.dispatchTouchEvent Dinge sind viel komplizierter. Es muss herausgefunden werden, welche der untergeordneten Ansichten das Ereignis erhalten soll (durch Aufrufen von child.dispatchTouchEvent). Hierbei handelt es sich im Wesentlichen um einen Hit-Test-Algorithmus, bei dem Sie herausfinden, welches Begrenzungsrechteck der untergeordneten Ansicht die Kontaktpunkt-Koordinaten enthält.
Bevor das Ereignis jedoch an die entsprechende untergeordnete Ansicht gesendet werden kann, können die Eltern das Ereignis gemeinsam ausspähen und/oder abfangen. Dafür gibt es onInterceptTouchEvent . Daher ruft sie diese Methode zuerst auf, bevor der Treffertest ausgeführt wird. Wenn das Ereignis entführt wurde (durch Rückgabe von true von onInterceptTouchEvent), sendet es ein ACTION_CANCEL an die untergeordneten Ansichten, damit diese ihre Berührungsereignisverarbeitung (von vorherigen Berührungsereignissen) aufgeben können und von da an werden alle Berührungsereignisse auf der übergeordneten Ebene an onTouchListener.onTouch (falls definiert) oder onTouchEvent () gesendet. OnInterceptTouchEvent wird auch in diesem Fall nie mehr aufgerufen.
Möchten Sie [Activity | ViewGroup | View] .dispatchTouchEvent überhaupt überschreiben? Wenn Sie kein benutzerdefiniertes Routing durchführen, sollten Sie dies wahrscheinlich nicht tun.
Die wichtigsten Erweiterungsmethoden sind ViewGroup.onInterceptTouchEvent, wenn Sie das Berührungsereignis auf übergeordneter Ebene ausspionieren und/oder abfangen möchten, und View.onTouchListener/View.onTouchEvent für die Behandlung von Hauptereignissen.
Alles in allem ist sein übermäßig kompliziertes Design imo, aber Android-Apps neigen eher zu Flexibilität als zu Einfachheit.
Weil dies das erste Ergebnis bei Google ist. Ich möchte mit Ihnen einen tollen Vortrag von Dave Smith über Youtube: Mastering the Android Touch System und die Folien sind verfügbar hier . Es gab mir ein gutes tiefes Verständnis über das Android Touch System:
Wie die Aktivität mit Berührung umgeht:
Activity.dispatchTouchEvent()
- Immer zuerst angerufen werden
- Sendet das Ereignis an die Stammansicht, die an Window angehängt ist
onTouchEvent()
- Wird aufgerufen, wenn keine Aufrufe das Ereignis verbrauchen
- Immer zuletzt angerufen werden
Wie die Ansicht mit Berührung umgeht:
View.dispatchTouchEvent()
- Sendet das Ereignis zuerst an den Listener, falls vorhanden
View.OnTouchListener.onTouch()
- Wird es nicht konsumiert, verarbeitet es die Berührung selbst
View.onTouchEvent()
Wie eine ViewGroup mit Berührungen umgeht:
ViewGroup.dispatchTouchEvent()
onInterceptTouchEvent()
- Überprüfen Sie, ob es Kinder ersetzen soll
- Übergibt
ACTION_CANCEL
An das aktive Kind- Return true einmalig, verbraucht alle nachfolgenden Ereignisse
- Für jede untergeordnete Ansicht wurden sie in umgekehrter Reihenfolge hinzugefügt
- Wenn Berührung relevant ist (Innenansicht),
child.dispatchTouchEvent()
- Wenn nicht durch vorherige behandelt, Versand zur nächsten Ansicht
- Wenn keine Kinder mit dem Ereignis umgehen, hat der Zuhörer eine Chance
OnTouchListener.onTouch()
- Wenn kein Zuhörer oder nicht behandelt
onTouchEvent()
- Abgefangene Ereignisse springen über untergeordnete Schritte
Er bietet auch einen Beispielcode für benutzerdefinierte Berührungen unter github.com/devunwired/ .
Antwort: Grundsätzlich wird die Funktion dispatchTouchEvent()
auf jeder Ebene View
aufgerufen, um festzustellen, ob eine View
ist an einer andauernden Geste interessiert. In einem ViewGroup
kann der ViewGroup
die Berührungsereignisse in seiner dispatchTouchEvent()
- Methode stehlen, bevor er dispatchTouchEvent()
für die Kinder aufruft. Die ViewGroup
würde den Versand nur dann stoppen, wenn die ViewGroup
onInterceptTouchEvent()
- Methode true zurückgibt. Der Unterschied ist, dass dispatchTouchEvent()
MotionEvents
sendet und onInterceptTouchEvent
sagt, ob es abfangen soll (MotionEvent
nicht an Kinder versenden) oder nicht (an Kinder versenden) .
Sie können sich vorstellen, dass Code einer ViewGroup ungefähr so funktioniert (sehr vereinfacht):
public boolean dispatchTouchEvent(MotionEvent ev) {
if(!onInterceptTouchEvent()){
for(View child : children){
if(child.dispatchTouchEvent(ev))
return true;
}
}
return super.dispatchTouchEvent(ev);
}
Hier sind einige visuelle Ergänzungen zu den anderen Antworten. Meine vollständige Antwort ist hier .
Die dispatchTouchEvent()
-Methode einer ViewGroup
verwendet onInterceptTouchEvent()
, um zu entscheiden, ob sie das Berührungsereignis sofort behandeln soll (mit onTouchEvent()
) oder die dispatchTouchEvent()
-Methoden seiner Kinder weiterhin benachrichtigen soll.
Es gibt viel Verwirrung über diese Methoden, aber es ist eigentlich nicht so kompliziert. Die meiste Verwirrung ist, weil:
View/ViewGroup
oder eines seiner untergeordneten Elemente in onTouchEvent
nicht als wahr zurückgegeben wird, werden NUR dispatchTouchEvent
und onInterceptTouchEvent
BE für MotionEvent.ACTION_DOWN
aufgerufen. Ohne ein Wahr von onTouchEvent
geht die übergeordnete Ansicht davon aus, dass Ihre Ansicht MotionEvents nicht benötigt wird.MotionEvent.ACTION_DOWN
aufgerufen, auch wenn Ihre ViewGroup in onTouchEvent
true zurückgibt.Bearbeitungsreihenfolge ist wie folgt:
dispatchTouchEvent
wird aufgerufen.onInterceptTouchEvent
wird für MotionEvent.ACTION_DOWN
aufgerufen, oder wenn Eines der untergeordneten Elemente der ViewGroup in onTouchEvent
den Wert true zurückgegeben hat.onTouchEvent
wird zuerst für die Kinder der ViewGroup aufgerufen und Wenn keines der Kinder wahr zurückgibt, wird es für View/ViewGroup
aufgerufen.Wenn Sie eine Vorschau von TouchEvents/MotionEvents
anzeigen möchten, ohne die Ereignisse für Ihre Kinder zu deaktivieren, müssen Sie zwei Dinge tun:
dispatchTouchEvent
, um eine Vorschau des Ereignisses anzuzeigen und zurückzukehren super.dispatchTouchEvent(ev)
;onTouchEvent
und geben Sie true zurück. Andernfalls erhalten Sie keine MotionEvent
-Werte 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:
onInterceptTouchEvent
den Wert true zurück, wenn Ihr Flag auf Abbrechen gesetzt ist MotionEvent-Verarbeitung durch Ihre Kinder. Dies ist auch ein bequemerplace zum Zurücksetzen Ihres Flags, da onInterceptTouchEvent nicht bis zum nächsten MotionEvent.ACTION_DOWN
erneut aufgerufen wird Beispiel für Überschreibungen in einem FrameLayout
(mein Beispiel in C # ist, da ich mit Xamarin Android programmiere, aber die Logik ist in Java die gleiche):
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;
}
Ich habe auf dieser Webseite http://doandroids.com/blogs/tag/codeexample/ eine sehr intuitive Erklärung erhalten. 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 Touch-Ereignis mit dieser ViewGroup oder einem untergeordneten Element davon als Ziel erkannt wird. Wenn diese Funktion true zurückgibt, wird das MotionEvent abgefangen, dh es wird nicht an das untergeordnete Objekt, sondern an das onTouchEvent dieser Ansicht weitergeleitet.
dispatchTouchEvent behandelt vor onInterceptTouchEvent.
Verwenden Sie dieses einfache Beispiel:
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 Log so aussieht:
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 der ersten Instanz zu behandeln, das zu onInterceptTouchEvent wechselt.
Ein weiterer Unterschied besteht darin, dass bei der Ausgabe von dispatchTouchEvent 'false' das Ereignis nicht an das untergeordnete Objekt weitergegeben wird, in diesem Fall EditText. Wenn Sie jedoch in onInterceptTouchEvent den Wert false zurückgeben, wird das Ereignis immer noch an den EditText gesendet
Die Antwort finden Sie in diesem Video https://www.youtube.com/watch?v=SYoN-OvdZ3M&list=PLonJJ3BVjZW6CtAMbJz1XD8ELUs1KXaTD&index=19 und den nächsten 3 Videos. Alle Touch-Events werden sehr gut erklärt, es ist sehr klar und voller Beispiele.
Kurze Antwort: dispatchTouchEvent()
wird an erster Stelle von allen aufgerufen.
Kurzer Hinweis: sollte dispatchTouchEvent()
nicht überschreiben, da es schwer zu kontrollieren ist. Manchmal kann dies die Leistung beeinträchtigen. IMHO schlage ich vor, onInterceptTouchEvent()
zu überschreiben.
Da die meisten Antworten das Flow-Touch-Ereignis für Aktivität/Ansichtsgruppeansicht ziemlich eindeutig erwähnen, füge ich in ViewGroup
nur weitere Details zum Code dieser Methoden hinzu (ignoriert dispatchTouchEvent()
):
/ - onInterceptTouchEvent()
wird zuerst aufgerufen, das ACTION-Ereignis wird aufgerufen bzw. abwärts -> bewegen -> aufwärts. Es gibt zwei Fälle:
Wenn Sie false in 3 Fällen zurückgeben (ACTION_DOWN, ACTION_MOVE, ACTION_UP), wird dies in Betracht gezogen, da der Elternteil dieses Berührungsereignis nicht benötigt , alsoonTouch()
der Eltern niemals , aber ruftonTouch()
der Kinder wird stattdessen anrufen ; Bitte beachten Sie jedoch:
onInterceptTouchEvent()
empfängt weiterhin ein Berührungsereignis, solange seine Kinder nicht requestDisallowInterceptTouchEvent(true)
aufrufen. onTouch()
der Eltern zurück.Umgekehrt, wenn Sie true zurückgeben , stiehlt der Elternteil dieses Berührungsereignis sofort und onInterceptTouchEvent()
stoppt sofort, stattdessen werdenonTouch()
der Eltern sowie alleonTouch()
aufgerufen. von Kindern erhalten das letzte Aktionsereignis - ACTION_CANCEL (dies bedeutet, dass die Eltern das Berührungsereignis gestohlen haben, und Kinder können es von da an nicht mehr handhaben). Der Ablauf von onInterceptTouchEvent()
return false ist normal, aber es gibt ein wenig Verwirrung mit return true case, daher liste ich es hier auf:
onTouch()
der Eltern erhalten ACTION_DOWN again und folgende Aktionen (ACTION_MOVE, ACTION_UP).onTouch()
der Eltern next ACTION_MOVE (nicht dasselbe ACTION_MOVE in onInterceptTouchEvent()
) und die folgenden Aktionen (ACTION_MOVE, ACTION_UP).onTouch()
der ElternNICHTüberhaupt aufgerufen, da es für die Eltern zu spät ist, das Berührungsereignis zu stehlen.Eine weitere Sache wichtig ist: 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 onTouch()
true zurückgibt, bedeutet dies, dass die Ansicht bereit ist, weitere Aktionen von diesem Ereignis zu erhalten. Andernfalls wird bei ACTION_DOWN in onTouch()
der Wert false zurückgegeben. Dies bedeutet, dass die Ansicht keine weitere Aktion von diesem Ereignis erhält.
Der folgende Code in einer ViewGroup-Unterklasse verhindert, dass die übergeordneten 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 einer benutzerdefinierten Überlagerung verwendet, um zu verhindern, dass Hintergrundansichten auf Berührungsereignisse reagieren.
Der Hauptunterschied :
• Activity.dispatchTouchEvent (MotionEvent) - Dies ermöglicht Ihre Aktivität um alle Berührungsereignisse abzufangen, bevor sie an die .__ gesendet werden. Fenster.
• ViewGroup.onInterceptTouchEvent (MotionEvent) - Dies ermöglicht eine ViewGroup, um Ereignisse zu überwachen, wenn sie an untergeordnete Ansichten gesendet werden.
ViewGroups onInterceptTouchEvent()
ist immer der Eintrittspunkt für das ACTION_DOWN
-Ereignis, das das erste Ereignis ist.
Wenn ViewGroup diese Geste verarbeiten soll, geben Sie true von onInterceptTouchEvent()
..__ zurück. Wenn true zurückgegeben wird, erhält onTouchEvent()
von ViewGroup alle nachfolgenden Ereignisse bis zum nächsten ACTION_UP
oder ACTION_CANCEL
und in den meisten Fällen den Berührungsereignissen zwischen ACTION_DOWN
und ACTION_UP
oder ACTION_CANCEL
sind ACTION_MOVE
, die normalerweise als Scroll-/Fling-Gesten erkannt werden.
Wenn Sie false von onInterceptTouchEvent()
zurückgeben, wird die onTouchEvent()
der Zielansicht aufgerufen. Es wird für nachfolgende Nachrichten wiederholt, bis Sie von onInterceptTouchEvent()
als wahr zurückgegeben werden.
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume =false;
if(onInterceptTouchEvent(ev){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
}
Meiner Meinung nach ist dispatchTouchEvent()
ein sehr wichtiger Teil beim Umgang mit Berührungen auf einem Gerät. Activity
, ViewGroup
, View
haben eine Realisierung dieser Methode. Wenn wir die Methode von ViewGroup
berücksichtigen, werden wir sehen, dass dispatchTouchEvent()
onInterceptTouchEvent()
aufruft.
Die vollständige SO Antwort lautet hier
Sowohl Activity als auch View haben die Methode dispatchTouchEvent () und onTouchEvent. Die ViewGroup hat auch diese Methoden, jedoch eine andere Methode, die onInterceptTouchEvent heißt. Der Rückgabetyp dieser Methoden ist boolean. Sie können die Versandroute über den Rückgabewert steuern.
Der Event-Versand in Android beginnt mit Activity-> ViewGroup-> View.