In diesem Artikel erfahren Sie mehr über die Verteilung von Berührungsereignissen. Der spezifische Inhalt ist wie folgt:
Es gibt insgesamt drei Arten von Berührungsaktionen: ACTION_DOWN, ACTION_MOVE, ACTION_UP. Wenn der Finger des Benutzers den Bildschirm berührt, wird ein Berührungsereignis mit der Aktion ACTION_DOWN generiert. Wenn der Finger des Benutzers zu diesem Zeitpunkt den Bildschirm sofort verlässt, wird ein Berührungsereignis mit der Aktion ACTION_UP generiert Wenn die Gleitdistanz die im System vordefinierte Distanzkonstante überschreitet, wird ein Berührungsereignis mit der Aktion ACTION_MOVE generiert. Die im System vordefinierte Distanzkonstante wird ermittelt, ob das Gleiten des Fingers des Benutzers auf dem Bildschirm zulässig ist Die Aktion ACTION_MOVE heißt TouchSlop und kann über ViewConfiguration get(getContext()).getScaledTouchSlop() übergeben werden. (2) Ereignissequenz Wenn der Finger des Benutzers den Bildschirm berührt, über den Bildschirm gleitet und dann den Bildschirm verlässt, generiert dieser Vorgang eine Reihe von Berührungsereignissen: ACTION_DOWN--> ; Mehrere ACTION_MOVE -->ACTION_UP. Diese Reihe von Berührungsereignissen ist eine Ereignissequenz. 2. Verteilung von Berührungsereignissen (1) Übersicht Wenn eine Berührungszeit auftritt, ist das System dafür verantwortlich, das Berührungsereignis an eine View (TargetView)-Verarbeitung weiterzugeben Der Prozess der Übergabe von Berührungsereignissen an TargetView ist die Verteilung von Berührungsereignissen. Verteilungsreihenfolge der Berührungsereignisse: Aktivität-->Ansicht der obersten Ebene-->Untergeordnete Ansicht der obersten Ebene--> .-->Zielansicht Touch-Ereignis-Antwortsequenz: TargetView's übergeordneter Container -->Aktivität Der spezifische Prozess der Touch-Ereignisverteilung a. Verteilung der Berührungsereignisse nach Aktivität Wenn der Finger des Benutzers den Bildschirm berührt, wird zunächst ein Berührungsereignis generiert, das das Berührungsereignis kapselt Die Methode „dispatchTouchEvent“ der Aktivität ist für die Verteilung des Berührungsereignisses verantwortlich. Die eigentliche Arbeit der Verteilung von Berührungsereignissen wird vom Fenster der aktuellen Aktivität erledigt, und das Fenster übergibt die Berührungsereignisse an DecorView (die oberste Ansicht der aktuellen Benutzeroberfläche). Der Code der DispatchTouchEvent-Methode von Activity lautet wie folgt:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
//Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { //Throw away all previous state when starting a new touch gesture. //The framework may have dropped the up or cancel event for the previous gesture due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); resetTouchState(); }
//Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWM || 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. intercept =true; }
这里存在一种特殊情形,就是子View通过requestDisallowInterceptTouchEvent方法设置父容器的FLAG_DISALLOW_INTERCEPT为true,这个标记指示是否不允许父容器拦截,为true表示不允许。这样做能够禁止父容器拦截除ACTION_DOWN以外的所有touch事件。之所以不能够拦截ACTION_DOWN事件,是因为每当ACTION_DOWN事件到来时,都会重置FLAG_DISALLOW_INTERCEPT这个标记位为默认值(false),所以每当开始一个新touch事件序列(即到来一个ACTION_DOWN动作),都会通过调用onInterceptTouchEven询问ViewGroup是否拦截此事件。当ACTION_DOWN事件到来时,重置标记位的工作是在上面的第一阶段完成的。
接下来,会进入第三阶段的工作:
final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { // 不是ACTION_CANCEL并且不拦截 if (actionMasked == MotionEvent.ACTION_DOWN) { // 若当前事件为ACTION_DOWN则去寻找这次事件新出现的touch target final int actionIndex = ev.getActionIndex(); // always 0 for down ... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { // 根据触摸的坐标寻找能够接收这个事件的touch target final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); final View[] children = mChildren; // 遍历所有子View for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = i; final View child = children[childIndex]; // 寻找可接收这个事件并且touch事件坐标在其区域内的子View if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); // 找到了符合条件的子View,赋值给newTouchTarget if (newTouchTarget != null) { //Child is already receiving touch within its bounds. //Give it the new pointer in addition to ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); // 把ACTION_DOWN事件传递给子组件进行处理 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { //childIndex points into presorted list, find original index for (int j=0;j<childrenCount;j++) { if (children[childIndex]==mChildren[j]) { mLastTouchDownIndex=j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //把mFirstTouchTarget赋值为newTouchTarget,此子View成为新的touch事件的起点 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } } }
当ViewGroup不拦截本次事件,则touch事件会分发给它的子View进行处理,相关代码从第21行开始:遍历所有ViewGroup的子View,寻找能够处理此touch事件的子View,若一个子View不在播放动画并且touch事件坐标位于其区域内,则该子View能够处理此touch事件,并且会把该子View赋值给newTouchTarget。
若当前遍历到的子View能够处理此touch事件,就会进入第38行的dispatchTransformedTouchEvent方法,该方法实际上调用了子View的dispatchTouchEvent方法。dispatchTransformedTouchEvent方法中相关的代码如下:
if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); }
若dispatchTransformedTouchEvent方法传入的child参数不为null,则会调用child(即处理touch事件的子View)的dispatchTouchEvent方法。若该子View的dispatchTouchEvent方法返回true,则dispatchTransformedTouchEvent方法也会返回true,则表示成功找到了一个处理该事件的touch target,会在第55行把newTouchTarget赋值给mFirstTouchTarget(这一赋值过程是在addTouchTarget方法内部完成的),并跳出对子View遍历的循环。若子View的dispatchTouchEvent方法返回false,ViewGroup就会把事件分发给下一个子View。
若遍历了所有子View后,touch事件都没被处理(该ViewGroup没有子View或是所有子View的dispatchTouchEvent返回false),ViewGroup会自己处理touch事件,相关代码如下:
if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
由以上代码可知,ViewGroup自己处理touch事件时,会调用dispatchTransformedTouchEvent方法,传入的child参数为null。根据上文的分析,传入的chid为null时,会调用super.dispatchTouchEvent方法,即调用View类的dispatchTouchEvent方法。
c. View对touch事件的处理
View的dispatchTouchEvent方法的主要代码如下:
public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; . . . if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } . . . return result; }
由上述代码可知,View对touch事件的处理过程如下:由于View不包含子元素,所以它只能自己处理事件。它首先会判断是否设置了OnTouchListener,若设置了,会调用onTouch方法,若onTouch方法返回true(表示该touch事件已经被消耗),则不会再调用onTouchEvent方法;若onTouch方法返回false或没有设置OnTouchListener,则会调用onTouchEvent方法,onTouchEvent对touch事件进行具体处理的相关代码如下:
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch (event.getAction()) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { . . . if (!mHasPerformedLongPress) { //This is a tap, so remove the longpress check removeLongPressCallback(); //Only perform take click actions if we were in the pressed state if (!focusTaken) { //Use a Runnable and post this rather than calling performClick directly. //This lets other visual state of the view update before click actions start. if (mPerformClick == null) { mPerformClck = new PeformClick(); } if (!post(mPerformClick)) { performClick(); } } } . . . } break; } . . . return true; }
由以上代码可知,只要View的CLICKABLE属性和LONG_CLICKABLE属性有一个为true(View的CLICKABLE属性和具体View有关,LONG_CLICKABLE属性默认为false,setOnClikListener和setOnLongClickListener会分别自动将以上俩属性设为true),那么这个View就会消耗这个touch事件,即使这个View处于DISABLED状态。若当前事件是ACTION_UP,还会调用performClick方法,该View若设置了OnClickListener,则performClick方法会在其内部调用onClick方法。performClick方法代码如下:
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this); result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
以上是我学习Android中触摸事件分发后的简单总结,很多地方叙述的还不够清晰准确
更多Android Touch事件分发深入了解相关文章请关注PHP中文网!