Android-Entwicklung, Touch ist überall. Einige Schüler, die nicht viel über Quellcode wissen, haben möglicherweise Zweifel daran. Der Verteilungsmechanismus von View-Ereignissen tritt nicht nur bei der Erfüllung geschäftlicher Anforderungen auf diese Probleme auf, sondern wird auch häufig in einigen schriftlichen Interviewfragen gestellt Es kann als alltäglich bezeichnet werden. Ich habe schon viele Artikel gelesen, die von Leuten in diesem Bereich geschrieben wurden. Sie sind entweder zu langatmig oder zu vage, und einige Details sind umstritten, daher werde ich diesen Inhalt noch einmal neu ordnen und Sie in zehn Minuten darüber informieren. Ereignisverteilungsmechanismus anzeigen.
Um es ganz klar auszudrücken: Der Ereignisverteilungsmechanismus dieser Berührungsereignisse besteht darin, die drei Methoden „dispatchTouchEvent()“, „OnInterceptTouchEvent()“ und „onTouchEvent()“ herauszufinden und das Problem des Stapelns dieser drei Methoden mit n ViewGroups zu lösen und Views: Egal wie komplex die Struktur ist, sie kann in 1 ViewGroup + 1 View aufgeteilt werden.
Tatsächlich sind ViewGroup und View ähnlich, es gibt also keine Abfangprobleme. Wenn Sie also ViewGroup verstehen, werden Sie es fast verstehen.
Drei Schlüsselmethoden
Public Boolean DispatchTouchEvent(MotionEvent ev)
View/ViewGroup verwaltet den Initiator der Ereignisverteilung. View/ViewGroup empfängt das Berührungsereignis und sendet es zuerst. Dies ist die Methode, die startet, und dann wird in dieser Methode beurteilt, ob das Abfangen verarbeitet oder das Ereignis an den Untercontainer verteilt werden soll
public boolean onInterceptTouchEvent(MotionEvent ev)
Speziell für Mit dieser Methode kann die Verteilungsrichtung von ViewGroup erreicht werden. Im Allgemeinen kann diese Methode verwendet werden, um zu bestimmen, ob das Ereignis allein an die ViewGroup übergeben werden soll Ereigniskonflikte behandeln
public boolean onTouchEvent(MotionEvent event)
Der eigentliche Handler von Berührungsereignissen, jedes Ereignis wird hier am Ende verarbeitet
Das Kernproblem
Was sind meiner Meinung nach die Schwierigkeiten des Zeitverteilungsmechanismus? Punkt: Drei Methodenaufrufregeln zur Bestimmung des Objekts, das das Ereignis verarbeitet, und zur Lösung des Ereigniskonflikts.
Regeln für die Zustellung von Ereignissen
Im Allgemeinen gibt es eine Reihe von MotionEvents für einen Klick, die einfach unterteilt werden können in: down->move->….->move- >oben, wenn ein Ereignis an die ViewGroup verteilt wird, kann die Aufrufsequenz in der ViewGroup zwischen den oben genannten drei Methoden durch einen einfachen Codeabschnitt
MotionEvent ev;//down or move or up or others... viewgroup.dispatchTouchEvent(ev); public boolean dispatchTouchEvent(MotionEvent ev){ boolean isConsumed = false; if(onInterceptTouchEvent(ev)){ isCousumed = this.onTouchEvent(ev); }else{ isConsumed = childView.dispatchTouchEvent(ev); } return isConsumed; }
ausgedrückt werden Die Rückgabe des Ergebnisses „true“ bedeutet, dass das Ereignis verarbeitet wurde, die Rückgabe von „false“ bedeutet keine Verarbeitung. Der obige Code ist leicht zu verstehen und sieht sehr einfach aus. Nachdem die ViewGroup das Ereignis empfangen hat, prüft sie zunächst, ob sie das Ereignis abfängt. Andernfalls wird es an jemanden übergeben, der über die Verarbeitung von Subcontainern verfügt.
Allerdings ist es einfach so geschrieben, dass der Ereignisverarbeitungsprozess von ViewGroup nicht so einfach ist. Ich werde sie hier weiter hinzufügen. Um auf das oben Gesagte zurückzukommen: Eine Reihe von Ereignissen, mit denen wir uns häufig befassen, sind normalerweise ein Down, mehrere Moves und ein Up. Der obige Pseudocode allein kann diese Probleme nicht perfekt lösen.
onInterceptTouchEvent-Aufrufbedingung
final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }
Erklären Sie den obigen Code. Er scheint sehr einfach zu sein, ist aber wirklich einfach. . Bevor wir es erklären, wollen wir über die Bedeutung von intercepted == false sprechen, was bedeutet, dass die ViewGroup des übergeordneten Containers das Ereignis nicht vorübergehend abfängt und das Ereignis zur Verarbeitung an die untergeordnete Ansicht übergeben werden kann Der Container hat die Ereignisreihe direkt abgefangen und wird sie später nicht an die untergeordnete Ansicht weitergeben. Wenn die Unteransicht das Ereignis erhalten möchte, kann sie nur den Wert auf „false“ setzen.
onInterceptTouchEvent-Aufruf gibt „false“ zurück (die Rückgabe von „false“ kann an die Unteransicht übergeben werden, was dem Inhalt in else des Pseudos entspricht). Code oben. Um das Ereignis an den Untercontainer weiterzugeben, müssen die folgenden Anforderungen erfüllt sein: Der Inhalt ist besser verständlich.) Es kann ausgelöst werden, wenn eine von zwei Bedingungen erfüllt ist (natürlich ist dies nur möglich):
Eines ist, wenn es ausgefallen ist, und das andere ist mFirstTouchTarget! =null, wann ist mFirstTouchTarget nicht leer? Interessierte Schüler können sich den Zeitpunkt des Aufrufs der addTouchTarget-Methode in ViewGroup ansehen. Der Quellcode ist zu lang und ich werde ihn nicht veröffentlichen.
mFirstTouchTarget wird verwendet, um die untergeordnete Ansicht in der ViewGroup zu speichern, die das ACTION_DOWN-Ereignis verbraucht hat, d. h. im obigen Pseudocode gibt child.dispatchTouchEvent(ev) true zurück, wenn ACTION_DOWN in ACTION_DOWN ist, solange die Der Versand der untergeordneten Ansicht erfolgt in ACTION_DOWN. Wenn true zurückgegeben wird, ist sie nicht null (dieser Zuweisungsprozess findet nur in ACTION_DOWN statt. Wenn die untergeordnete AnsichtACTION__DOWN ihr keinen Wert zuweist, wird die nachfolgende Ereignissequenz nicht erneut ausgeführt). Im Gegenteil, wenn keine Unteransichtsverarbeitung erfolgt, ist das Objekt null. Natürlich reicht es nicht aus, die beiden oben genannten Bedingungen zu erfüllen, Sie müssen auch!disallowIntercept erfüllen.
Die disallowIntercept-Variable wird hauptsächlich durch das Flag FLAG_DISALLOW_INTERCEPT beeinflusst. Dieser Wert kann von der Unteransicht von ViewGroup festgelegt werden Ändern Sie FLAG_DISALLOW_INTERCEPT, was zu disallowIntercept führt. Der Wert ist wahr. In diesem Fall wird das Abfangen übersprungen.
但这事还没了,FLAG_DISALLOW_INTERCEPT这个标记有一个重置的机制,查看ViewGroup源码可以看到,在处理MotionEvent.ACTION_DOWN的时候会重置这个标记导致disallowIntercept失效,是不是丧心病狂,上面的一段这么简单的代码有这么多幺蛾子,这里还能得到一个结论,ACTION_DOWN的时候肯定可以执行onInterceptTouchEvent的。
所以拦截的intercepted很重要,能影响到底是让ViewGroup还子View处理这个事件。
上面的两个有可能触发拦截的条件说完了,那么当两个条件都不满足的话就不会再调用拦截了(拦截很重要,一般ViewGroup都返回false这样能把事件传递给子View,如果在ACTION_DOWN时不能走到OnInterceptTouchEvent并返回false告诉ViewGroup不要拦截,则事件再也不能传给子View了,所以拦截一般都是要走到的,而且一般都是返回false这样能让子View有机会处理),这种情况一般都是在ACTION_DOWN处理完之后没有子View当接盘侠消费ACTION_DOWN以及后续事件,从上面的伪代码可以看出来,这时候ViewGroup自己就很被动了,需要自己来调用onTouchEvent来处理,这锅就自己背了。
再继续说一下mFirstTouchTarget和intercepted是怎么影响事件方向的。看源码:
if (!canceled && !intercepted) { .... if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { .... for(child : childList){ if(!child satisfied condition....){ continue; } newTouchTarget = addTouchTarget(child, idBitsToAssign);//在这里给mFirstTouchTarget赋值 } } }
可以在这里看到intercepted为false在ACTION_DOWN里才能给上面说过的mFirstTouchTarget赋值,只有mFirstTouchTarget不为空才能让后续事件传递给子View,否则根据上上面说的代码后续事件只能给父容器处理了。
mFirstTouchTarget就是我们后续事件传递的对象,很容易理解,如果在ACTION_DOWN中没有确定这个对象,则后续事件不知道传递给谁自然就交给父容器ViewGroup处理了,真正处理事件传递的方法是dispatchTransformedTouchEvent,再看源码:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } }
看到没,只要参数里传的child为空,则ViewGroup调用super.dispatchTouchEvent(event),super是谁,ViewGroup继承自View,当然是View咯,View的dispatch调用的谁?当然是自己的onTouchEvent(后面会说),所以这个最后还是调用了ViewGroup自己的onTouchEvent。
那么当child!=null的时候呢,调用的是child的dispatchTouchEvent(event),如果child可能是View也可能是ViewGroup,如果是ViewGroup则继续按照上面的伪代码执行事件分发,如果也是View则调用自己的onTouchEvent。
所以,说到底事件到底给谁处理,还是和传进来的child有关,那这个方法在哪里调用的呢,继续看:
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits) }
这就是为什么mFirstTouchTarget能影响事件分发的方向的原因。就这样,整个伪代码的流程是不是很清楚了。
这里需要多说两句,在上上面代码流程中,intercepted决定了这个事件会不会调用ViewGroup的onTouchEvent,当intercepted为true则后续流程会调用ViewGroup的onTouchEvent,仔细看上面的代码能发现,只有两种情况为ture:一是调用了InterceptTouchEvent把事件拦截下来,另一个就是没有一个子View能够消费ActionDown。只有这两种情况父容器ViewGroup才会自己处理
那么问题来了,思考一个问题:如果子View处理了ACTION_DOWN但后续事件都返回false,这些没有被处理的事件最后传给谁处理了?各位思考之,后面再说这个问题。
孩子是谁的
继续来扩展我们的伪代码,拦截条件判断完之后,决定把事件继续传递给子View的时候,会调用childView.dispatchTouchEvent(ev),问题来了,child是哪来的,继续看源码、
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; }
ViewGroup通过判断所有的子View是否可见是否在播放动画和是否在点击范围内来决定它是否能够有资格接受事件。只有满足条件的child才能够调用dispatch。
再看伪代码,最后dispatch返回ViewGroup的isConsumed,若isConsume == true,说明ViewGroup处理了这个点击事件(ViewGroup自身或者子View处理的),并且这个系列的点击事件会继续传到这个ViewGroup来处理,若isConsume == false(ACTION_DOWN时),ViewGroup没办法处理这个点击事件,那么这个系类的点击事件就和该ViewGroup无缘了。会把这个事件上抛给自己的父容器或者Activity处理。
伪代码说完了,ViewGroup的事件传递规则也就差不多说完了,这么看是不是很简单了。View相对于ViewGroup来说就更简单了,没有拦截方法,dispatch基本上是直接调用了自身的onTouchEvent,处理起来一点难度都木有呀。
一些没说到但也很重要的点
Die oben erklärten Dinge sind sehr einfach. Sie beginnen mit einer ViewGroup + einer View. Der Untercontainer hat jedoch nur eine View. Aber keine Angst, egal wie kompliziert es ist. Die Situation kann auch in diesen Modus aufgeteilt werden, aber die Ebenen sind rekursiver und komplizierter und das Prinzip ist immer noch dasselbe.
Übrigens noch ein paar zusätzliche Punkte:
Wenn eine Reihe von Klickereignissen dadurch ausgelöst wird, dass der Benutzer auf den Bildschirm klickt, ist der eigentliche Ereignisbereitstellungsprozess: Aktivität (PhoneWindow)-> DecorView->ViewGroup- >View, es gibt ein DecorView-Ereignis, das von der Aktivität übergeben wird, aber diese Dinge sind tatsächlich die gleichen wie die ViewGroup die in seiner DecorView enthaltenen untergeordneten Elemente. Wenn niemand in der Ansicht das Ereignis konsumieren kann (hier gibt es eine Lücke, verstehen Sie einfach, was ich meine), wird es schließlich zur Verarbeitung an die Aktivität übergeben.
Die Lösung von Ereigniskonflikten kann in mehreren Punkten gemäß den oben genannten Grundsätzen behandelt werden. Der einfachste Zeitpunkt, um über die Verarbeitungszeit nachzudenken, ist in onInterceptTouchEvent. Wenn beispielsweise eine vertikal verschiebbare ViewGroup in einer horizontal verschiebbaren ViewGroup verschachtelt ist, können Sie hier ACTION_MOVE verwenden, um zu bestimmen, an wen nachfolgende Ereignisse zur Verarbeitung übergeben werden sollen Gemäß dem oben erwähnten Flag-Bit FLAG_DISALLOW_INTERCEPT ist es einfacher, sich vorzustellen, den Ereignisfluss in Verbindung mit dem „dispatchTouchEvent“ der untergeordneten Ansicht zu steuern. Ich habe jedoch gesehen, dass andere Experten den Ereignisfluss durch die gemeinsame Nutzung der MotionEvent-Methode steuern. Das heißt, es kann im übergeordneten Container gespeichert werden. Es ist auch möglich, MotionEvent zu verwenden und zum geeigneten Zeitpunkt die benutzerdefinierte Ereignisbehandlungsmethode der untergeordneten Ansicht zu übergeben, um Ereignisse zu teilen.
Solange eine Ansicht ACTION_DOWN in einer Reihe von Ereignissen ablehnt (gibt false zurück), werden nachfolgende Ereignisse nicht übergangen. Wenn jedoch andere Ereignisse abgelehnt werden, können nachfolgende Ereignisse weiterhin übergeben werden. Wenn beispielsweise ACTION_MOVE nicht von der Ansicht verarbeitet wird, wird dieses unverarbeitete Ereignis schließlich von der Aktivität (nicht vom übergeordneten Container der Ansicht) verbraucht, nachfolgende Ereignisse jedoch wird weiterhin an die Ansicht übergeben.
Durch die ordnungsgemäße Verwendung von ACTION_CANCEL kann der Lebenszyklus einer Reihe von Ereignissen gesteuert und die Ereignisverarbeitung flexibler gestaltet werden.
Um den Mechanismus der Ereignisverteilung zu verstehen, reicht es im Grunde aus, die oben genannten Prinzipien zu verstehen. Sie können sich auch die Verteilung verschiedener cooler benutzerdefinierter Steuerelemente ansehen, die auf Github basieren. Sie können auch verstehen, dass es natürlich viele Erweiterungen und tiefergehende Inhalte gibt, auf die ich aus Platzgründen nicht näher eingehen werde. Wichtiger ist, sich den Quellcode anzusehen.
Abschließend möchte ich Ihnen noch ein klassisches Zitat geben: Was Sie auf dem Papier sehen, wird Ihnen irgendwann klar machen, dass Sie es tun müssen, aber Sie werden auf jeden Fall wissen, dass Sie es tun müssen!
Das Obige ist die Sammlung von Informationen zum Android View-Ereignisverteilungsmechanismus. Wir werden auch in Zukunft relevante Informationen hinzufügen. Vielen Dank für Ihre Unterstützung dieser Website.
Ausführlichere Erläuterungen zum Android View-Ereignisverteilungsmechanismus und zugehörige Artikel finden Sie auf der chinesischen PHP-Website!