ViewGroup的dispatchTouchEvent事件总线路:
public boolean dispatchTouchEvent(MotionEvent ev) {调用onInterceptTouchEvent检查是否拦截事件if(没有拦截){在ViewGroup中遍历查找目前是点击了哪个子视图if(找到了){调用该子视图的dispatchTouchEvent,递归下去}else{没找到,则将事件传给onTouchListener,没有Listener则传给onTouchEvent()如果再listener或者onTouchEvent()中down事件返回了true,代表事件被消费,后续的move和up都被Listener或者onTouchEvent()处理,如果down事件返回false,则后续的move,up事件将不会到这一层的Viewgroup,而直接在上一层视图被消费。}}else{事件被拦截了,原本被点击的子视图将接收到一个ACTION_CANCEL事件,而down事件传给onTouchListener,没有Listener则传给onTouchEvent(),依然遵从上面的down和move,up事件的关系}}
ViewGroup的onInterceptTouchEvent方法默认返回的是false,表示当前ViewGroup不对事件进行拦截;返回true表示对事件进行拦截,外面一层的子View将不会得到该事件的响应。
1.ViewGroup的dispatchTouchEvent中的ACTION_DOWN
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {if (!onFilterTouchEventForSecurity(ev)) {return false;}final int action = ev.getAction();final float xf = ev.getX();final float yf = ev.getY();final float scrolledXFloat = xf + mScrollX;final float scrolledYFloat = yf + mScrollY;final Rect frame = mTempRect;boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {if (mMotionTarget != null) {// this is weird, we got a pen down, but we thought it was// already down!// XXX: We should probably send an ACTION_UP to the current// target.mMotionTarget = null;}// If we're disallowing intercept or if we're allowing and we didn't// interceptif (disallowIntercept || !onInterceptTouchEvent(ev)) {// reset this event's action (just to protect ourselves)ev.setAction(MotionEvent.ACTION_DOWN);// We know we want to dispatch the event down, find a child// who can handle it, start with the front-most child.final int scrolledXInt = (int) scrolledXFloat;final int scrolledYInt = (int) scrolledYFloat;final View[] children = mChildren;final int count = mChildrenCount;for (int i = count - 1; i >= 0; i--) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE|| child.getAnimation() != null) {child.getHitRect(frame);if (frame.contains(scrolledXInt, scrolledYInt)) {// offset the event to the view's coordinate systemfinal float xc = scrolledXFloat - child.mLeft;final float yc = scrolledYFloat - child.mTop;ev.setLocation(xc, yc);child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;if (child.dispatchTouchEvent(ev)) {// Event handled, we have a target now.mMotionTarget = child;return true;}// The event didn't get handled, try the next view.// Don't reset the event's location, it's not// necessary here.}}}}} ....//other code omitted
16行:进入ACTION_DOWN的处理
17-23行:将mMotionTarget置为null
26行:进行判断:if(disallowIntercept || !onInterceptTouchEvent(ev))
两种可能会进入IF代码段
1、当前不允许拦截,即disallowIntercept =true,
2、当前允许拦截但是未进行拦截,即disallowIntercept =false,但是onInterceptTouchEvent(ev)返回false ;
注:disallowIntercept 可以通过viewGroup.requestDisallowInterceptTouchEvent(boolean)进行设置;而onInterceptTouchEvent(ev)可以进行复写。
36-57行:开始遍历所有的子View
41行:进行判断当前的x,y坐标是否落在子View身上,如果在47行执行child.dispatchTouchEvent(ev),就进入了View的dispatchTouchEvent代码中了,当child.dispatchTouchEvent(ev)返回true,则为mMotionTarget=child;然后return true;
ViewGroup的ACTION_DOWN分析结束,总结一下:
ViewGroup实现捕获到DOWN事件,如果代码中不做TOUCH事件拦截,则开始查找当前x,y是否在某个子View的区域内,如果在,则把事件分发下去。
2.ACTION_MOVE
@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {final int action = ev.getAction();final float xf = ev.getX();final float yf = ev.getY();final float scrolledXFloat = xf + mScrollX;final float scrolledYFloat = yf + mScrollY;final Rect frame = mTempRect;boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//...ACTION_DOWN//...ACTIN_UP or ACTION_CANCEL// The event wasn't an ACTION_DOWN, dispatch it to our target if// we have one.final View target = mMotionTarget;// if have a target, see if we're allowed to and want to intercept its// eventsif (!disallowIntercept && onInterceptTouchEvent(ev)) {//....}// finally offset the event to the target's coordinate system and// dispatch the event.final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;ev.setLocation(xc, yc);return target.dispatchTouchEvent(ev);}
18行:把ACTION_DOWN时赋值的mMotionTarget,付给target ;
23行:if (!disallowIntercept && onInterceptTouchEvent(ev)) 当前允许拦截且拦截了,才进入IF体,当然了默认是不会拦截的~这里执行了onInterceptTouchEvent(ev)
28-30行:把坐标系统转化为子View的坐标系统
32行:直接return target.dispatchTouchEvent(ev);
可以看到,正常流程下,ACTION_MOVE在检测完是否拦截以后,直接调用了子View.dispatchTouchEvent,事件分发下去;
3.ACTION_UP
public boolean dispatchTouchEvent(MotionEvent ev) {if (!onFilterTouchEventForSecurity(ev)) {return false;}final int action = ev.getAction();final float xf = ev.getX();final float yf = ev.getY();final float scrolledXFloat = xf + mScrollX;final float scrolledYFloat = yf + mScrollY;final Rect frame = mTempRect;boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (action == MotionEvent.ACTION_DOWN) {...}boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||(action == MotionEvent.ACTION_CANCEL);if (isUpOrCancel) {mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;}final View target = mMotionTarget;if(target ==null ){...}if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}if (isUpOrCancel) {mMotionTarget = null;}// finally offset the event to the target's coordinate system and// dispatch the event.final float xc = scrolledXFloat - (float) target.mLeft;final float yc = scrolledYFloat - (float) target.mTop;ev.setLocation(xc, yc);return target.dispatchTouchEvent(ev);}
17行:判断当前是否是ACTION_UP
21,28行:分别重置拦截标志位以及将DOWN赋值的mMotionTarget置为null,都UP了,当然置为null,下一次DOWN还会再赋值的~
最后,修改坐标系统,然后调用target.dispatchTouchEvent(ev);
正常情况下,即我们上例整个代码的流程我们已经走完了:
1、ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)
当然了在分发之前都会修改下坐标系统,把当前的x,y分别减去child.left 和 child.top ,然后传给child;
问题1:如何在ViewGroup中进行事件拦截?
默认是不拦截的,即返回false;如果你需要拦截,只要在onInterceptTouchEvent()中return true就行了,该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。
原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget 置为null ;
问题2:如果ViewGroup的onInterceptTouchEvent(ev) 当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;此时子View希望依然能够响应MOVE和UP时该咋办呢?
requestDisallowInterceptTouchEvent(boolean) 用于设置是否允许拦截,我们在子View的dispatchTouchEvent中直接通过getParent().requestDisallowInterceptTouchEvent(true)设置子view所在ViewGroup的disallowIntercept:
@Overridepublic boolean dispatchTouchEvent(MotionEvent event){getParent().requestDisallowInterceptTouchEvent(true);int action = event.getAction();switch (action){case MotionEvent.ACTION_DOWN:Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");break;case MotionEvent.ACTION_UP:Log.e(TAG, "dispatchTouchEvent ACTION_UP");break;default:break;}return super.dispatchTouchEvent(event);}
ViewGroup触摸事件解析
本文详细解析了ViewGroup中dispatchTouchEvent方法的工作原理,包括ACTION_DOWN、ACTION_MOVE和ACTION_UP等不同阶段的行为,以及如何在ViewGroup中进行事件拦截。
471

被折叠的 条评论
为什么被折叠?



