点击操作发生时,最先调用Activity的dispatchTouchEvent方法。public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
上面先调用Window的superDispatchTouchEvet方法,实际上是PhoneWindow的superDispatchTouchEvent,如果处理了就返回true则不会调用Activity的onTouchEvent方法来处理触摸事件。而PhoneWindow的superDispatchTouchEvent方法中实际调用类的是DecrView的superDispatchEvent方法。superDispatchEvent方法最终调用的是基类ViewGroup的DispatchEvent方法。在ViewGroup的DispatchEvent方法中代码较多,分段解析 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(); } 如果是ACTION_DOWN类型的点击事件,代表是一段新的事件序列(ACTION_DOWN,ACTION_MOVE,ACTION_UP),需要重置以前的状态,调用了cancelAndCleaTouchTargets()和resetTouchState();resetTouchState()里有这样一行代码将不拦截的标识位设置为空。 mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;所以ACTION_DOWN事件无法由子View要求不拦截。 // 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; } 检测是否打断传递,由自己处理触摸事件,如果事件不是ACTION_DOWN类型而是其他事件类型,且mFritstTouchTarget为空(表示没有子VIew处理触摸事件),则打断传递,自己处理触摸事件(intercepted=true)。如果条件不成立,则利用到前面所说的要求不拦截的标识位,看是否设置了(通过子View的requestDisallowInterceptTouchEvent方法),设置了就不拦截intercepted=false,否则就调用onIterceptToucheEvent方法给intercepted赋值。所以onIterceptToucheEvent中返回的boolean值也关系到是否拦截触摸事件。 从优先级来说,较高的是要求不拦截的标志位,其次才是onInterceptToucheEvent。也就是说如果设置了标志位(前提是在ActionDown事件被ViewGroup处理之前设置的),就算覆写的onInterceptToucheEvent返回true也没用。 最开始的判断是: 如果事件不是ACTION_DOWN类型且没有子VIew处理触摸事件(在后面代码可以发现,如果有子View处理触摸事件,mFirstTouchTarget会被赋值),那么intercepted=true.表明这个事件的起始ACTION_DOWN类型事件被当前ViewGroup消费了(如果是被子view消费了,mFistTouchTarget会被赋值),所以其他类型事件也会被拦截,这就是所谓的一旦一个事件序列的ACTION_DOWN被处理了,其他ACTION_UP,ACTION_MOVE都会交给这个ViewGroup处理。这个判断在最前面,所以优先级最高。高于设置要求不拦截的标志位,更高于onInterceptTouchEvent返回true。只要在onInterceptToucheEvent判断ActionDown事件返回true之后,之后再设置要求不拦截的标志位也没用了。不过不太可能出现这种情况。 总而言之结论如下: 1一旦一个事件序列的ACTION_DOWN被ViewGroup处理了,其他ACTION_UP,ACTION_MOVE都会交给这个ViewGroup处理(注意是对ViewGroup而不是View) 2设置要求不拦截的标识位优先级大于onInterceptToucheEvent方法。 如果1和2冲突了,1优先级大于2优先级。 if (!canceled && !intercepted) { //调用findChildWithAccessibilityFocus方法得到具有焦点的位于最前面的子View View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() findChildWithAccessibilityFocus() : null; final int childrenCount = mChildrenCount; //如果事件还没被处理(newTouchTarget为空),且子view个数不等于0 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. //得到点击事件的x,y坐标值(相对于ViewGroup)后,通过buildTouchDispatchChildList方法建立以view的z值大小排列的view列表
final ArrayList<View> preorderedList = buildTouchDispatchChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); //遍历ptrotfrtList的view。寻找能处理触摸事件的view // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. //找到前面具有焦点的子view,childWithAccessibiltyFocus==child,具有焦点的子view最优先处理触摸事件。所以直接跳到最后一个元素。结束循环。 if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } 上面这段代码说明当事件没被当前ViewGroup拦截的时候,优先由具有焦点的子view处理。比如ListView中项布局中的Button常常会抢了触摸事件。这是因为它具有焦点。后面会调用具有焦点的子view的dispatchTouchEvent。 //调用canViewReceivePointerEvents方法判断,如果view可见或者在执行动画则返回true,表示能处理 //触摸事件,isTransformedTouchPointInView方法判断触摸事件的坐标是否在view内,若不在则跳过 if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } //调用dispatchTransformedTouchEvent方法,里面调用子view的dispatchToucheEvent方法。可能是View的dispatchTouchEvent,也可能是ViewGroup的dispatchTouchEvent方法 if(dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) newTouchTarget = addTouchTarget(child, idBitsToAssign); 上面代码说明如果view不可见且不再执行动画则不能响应事件。如果是view的dispatchTouchEvent方法,在里面调用了view的onTouchEvent方法。如果dispatchTransformedTouchEvent方法返回true则表示事件已经被处理。调用addTouchTarget方法,在方法里面为mFirstTouchTarget赋值。 如果在循环中没有子view可以处理触摸事件或者canceled,intercepted为true(表示被取消,打断)还是会调用dispatchTransformedTouchEvent。不过传的子view参数为空。dispatchTransformedTouchEvent就会调用父view(View类型)的dispatchTouchEvent方法。调用ViewGroup的onTouchEvent方法(实际上是父类View的方法),从而形成事件的回传。如下: if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
再看看如何判断触摸事件的坐标在子View范围中。isTransformTouchPointerView方法 transformPointToViewLocal(point, child); final boolean isInView = child.pointInView(point[0], point[1]); 先调用transformPointToViewLocal将相对于父ViewGroup的坐标转换成相对于View(child也可能是viewGroup)的坐标。 然后调用view的pointInView方法判断是否在范围中。 public void transformPointToViewLocal(float[] point, View child) { point[0] += mScrollX - child.mLeft; point[1] += mScrollY - child.mTop; public boolean pointInView(float localX, float localY, float slop) { return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && localY < ((mBottom - mTop) + slop); } 可以看出transformPointToVIewLocal方法将父ViewGroup的mScrollx,mScrolly也计算在中。所以当父ViewGroup的通过Scroller移动的时候(Scroller改变mScrollx,mScolly,移动超出ViewGroup本身的部分将会被截断,属性动画改变translationX,)由于进行了计算所以ViewGroup里面的内容的子view会响应触摸事件,而ViewGroup本身也会响应触摸事件(作为上一层的子view响应)。而pointInView方法通过左右边距来计算是否在范围中。 VIew的dispatchTouchEvent方法中先是调用了TouchListener的onTouch方法,如果监听器被设置了,且View是设置了Enable的。onTouch设置result为true就不会调用onTouchEvent方法。可以看出TouchListener.onTouch优先级高于onTouchEvent方法。如果想要两个方法都调用,可以设置View的enable为false。最后返回result。 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; }
if (!result && onTouchEvent(event)) { result = true; } onTouchEvent方法中调用了performClick方法,performClick方法调用了ClickListener的onClick方法 public boolean onTouchEvent(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final int viewFlags = mViewFlags; final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } if (mTouchDelegate != null) { if (mTouchDelegate.onTouchEvent(event)) { return true; } }
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); }
if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); }
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // 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) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } } 从上面代码可知,设置了View的enable为false就不会调用onClick方法。但是消费了触摸事件。 调用onClick方法的前提是view的clickable或者longclickable为true或者contextclickable为true。
最后总结下view事件分发函数的优先级顺序 父viewgroup的dispatchTouchEvent 子view的requestDisallowInterceptTouchEvent 父viewgroup的onInterceptTouchEvent 子view的TouchListener.onTouch 子view的onTouchEvent 子view的ClickListener.onClick 整理下view的事件分发流程: 首先Activity调用DecorView的dispatchTouchEvent分发事件的开始,如果返回false就会调用Activity的onTouchEvent方法由Activity处理触摸事件。 DecorView作为一个ViewGroup(DecorView继承于FrameLayout) 它的dispatchTouchEvent首先判断自己是否处理了action_down事件,是的话就直接打断事件传递。不是根据子view设置的要求不拦截标识位决定是否调用ViewGroup的onInterceptTouchEvent,根据这些判断是否打断事件传递。如果打断则直接调用父类View的dispatchTouchEvent方法由自身来处理触摸事件,返回父类View的dispatchTouchEvent方法的返回值。根据返回值判断是否由上一层(这里DecorView的上一层是Activity)处理触摸事件。这样就能形成事件的回传。
不打断就遍历所有孩子,优先有焦点的,并根据(判断触摸事件是否发生在子view内和子view是否可见是否执行动画)来判断是否由子view可以处理事件,如果有则调用子view的dispatchTouchEvent方法(通过dispatchTransformedTouchEvent方法),如果没有就跳到打断的逻辑处,用父类View的dispatchTouchEvent方法自身处理触摸事件。除此之外,如果子view的dispatchTouchEvent方法也处理不了触摸事件,还是会跳到打断的逻辑处。 子view可以是view类型,也可以是viewgroup类型,viewgroup类型依然走上面的逻辑,view类型则走上面的逻辑中说的打断逻辑处的dispatchTouchEvent方法处理。 最后来比较下view和viewgroup的dispatchTouchEvent的区别: view分发事件直接进行事件处理,返回是否处理过的bool值 viewgroup分发事件要先进行子view的分发事件处理,子view的分发事件处理完毕,再根据情况(mFirstTouchTarget是否为空)决定是否要自己处理。
|