Android鬼点子-View的事件分发机制 | GreendaMi'Blog
今天来说说View的事件分发机制,其实这个问题也困扰了我很久,今天终于找了个事件,和大家一起把这里理顺!
一个完整的事件是从手指接触到屏幕开始,手指离开屏幕结束。即以down事件开始,然后可能是若干个move事件,最后是up。
首先,一个事件是这样传递的:Activity->Window(PhoneWindow)->View。其中View中的事件传递机制如下:ViewGroup->View->子View。如果一个子View没有处理收到的事件(onTouchEvent返回false),那么这个事件会交给它的上级处理。如果所有元素都不处理这个事件,那么最后出手的会是Activity的onTouchEvent。
其次,点击事件的分发是由下面3个很重要的方法共同决定的。
public boolean dispatchTouchEvent(MotionEvent ev); 用来进行事件分发表示是否消耗当前事件。
public boolean onInterceptTouchEvent(MotionEvent ev); 判断是否拦截某个点击事件 返回true就拦截 false不拦截向下传递,View中没有这个方法。
public boolean onTouchEvent(MotionEvent ev); 用来处理点击事件。
在ViewGroup源码的dispatchTouchEvent中有下面这样一段代码,作用是判断是ViewGroup自己处理这个事件(拦截),还是子View处理这个事件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // Check for interception. 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; } |
intercepted这个变量是设置这个ViewGroup是否拦截。actionMasked == MotionEvent.ACTION_DOWN这句,验证了一个事件的处理是从down事件开始,如果一开始的down事件这个ViewGroup没有插手,那么接下来的move和up事件,这个ViewGroup都不会拦截。mFirstTouchTarget这个东西如果不是null,说明这个事件被它的子View成功处理了。这里指向的是它的子View。
FLAG_DISALLOW_INTERCEPT这个标志位是在子View中设置的,一旦设置上了,这个ViewGroup就不会拦截除了down事件意外的事件,而这个标志位会在down事件时被重置。
下面是不拦截的情况,事件会扔给它的子View处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | if (!canceled && !intercepted) { if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final View[] children = mChildren; final boolean customOrder = isChildrenDrawingOrderEnabled(); for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = children[childIndex]; if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { continue; } newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); mLastTouchDownIndex = childIndex; mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } } if (newTouchTarget == null && mFirstTouchTarget != null) { // Did not find a child to receive the event. // Assign the pointer to the least recently added target. newTouchTarget = mFirstTouchTarget; while (newTouchTarget.next != null) { newTouchTarget = newTouchTarget.next; } newTouchTarget.pointerIdBits |= idBitsToAssign; } } } |
上面首先判断了事件的坐标是否在某个View的地盘内(是否落在子元素区域内),canViewReceivePointerEvents和isTransformedTouchPointInView来判断的。dispatchTransformedTouchEvent这个就是调用子View的dispatchTouchEvent,如果子View的dispatchTouchEvent返回true,那么这个事件就交给子View处理了,然后
1 2 3 | newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; |
1 2 3 4 5 6 | private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; } |
这里就会给mFirstTouchTarget赋上值,并且跳出循环。
如果没有子View处理或者子View处理了,但是dispatchTouchEvent返回false,ViewGroup就会自己处理这个事件,这里第3个参数子View是null。
1 2 3 4 5 6 | // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } |
下面就是View中的处理了。先看一下View的dispatchTouchEvent源码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public boolean dispatchTouchEvent(MotionEvent event) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { return true; } if (onTouchEvent(event)) { return true; } } if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } return false; } |
明显比ViewGroup简单多了,View不会把事件继续传递下去。如果View设置了OnTouchListener,那么OnTouchListener的onTouch就会被调用,如果onTouch返回true,那么View的onTouchEvent将不会被调用。所以OnTouchListener的onTouch的优先级大于View的onTouchEvent。最后常用的onClickListener是在View的onTouchEvent中被调用的。
最后总结一下,dispatchTouchEvent是告诉你的领导这件事是不是交给你来做,返回true,交给你;返回false,这事我不管……你去找别人。无论这件事归不归你管,领导问你能不能做的时候,你总要说句话吧?所以当一个ViewGroup或者View接受到事件时dispatchTouchEvent总会被调用。onInterceptTouchEvent是告诉你的手下的小弟刚才领导交的差事是你亲自做,还是小弟们做,你亲自做返回true,然后调用你自己的onTouchEvent,返回false,交给小弟做。