发布时间:2020-09-10 22:22:12来源:Android编程精选
Python实战社群
Java实战社群
长按识别下方二维码,按需求添加
扫码关注添加客服
进Python社群▲
扫码关注添加客服
进Java社群▲
作者丨张潮州
来源丨Android技术堆栈
接着上篇《Activity对触摸事件的分发流程》,这篇我们讲《ViewGroup和View对触摸事件的分发流程》。
由于文章的代码篇幅较长,建议使用电脑阅读。
首先是Activity中dispatchTouchEvent()方法returnsuper之后将会调用ViewGroup的dispatchTouchEvent()方法:
1、如果onInterceptTouchEvent()方法返回false,将通过buildTouchDispatchChildList()方法获取所有能接收该事件的子视图;而后如果dispatchTransformedTouchEvent()方法返回true,将通过addTouchTarget()生成mFirstTouchTarget对象。
2、如果mFirstTouchTarget为空,说明此时子视图为View,将通dispatchTransformedTouchEvent()继续分发事件;如果mFirstTouchTarget不为空,说明此时子视图为ViewGroup,将遍历ViewGroup的子视图通过dispatchTransformedTouchEvent()继续分发事件。
3、dispatchTransformedTouchEvent()方法将通过View的dispatchTouchEvent()方法继续分发事件。
ViewGroup.java
@OverridepublicbooleandispatchTouchEvent(MotionEventev){booleanhandled=false;//为了安全性过滤触摸事件if(onFilterTouchEventForSecurity(ev)){finalintaction=ev.getAction();//0xff动作代码中动作本身部分的位掩码。属于多点触碰。finalintactionMasked=action&MotionEvent.ACTION_MASK;//处理最初的DOWN事件。if(actionMasked==MotionEvent.ACTION_DOWN){//当开始一个新的触摸手势时扔掉所有以前的状态。//由于应用程序切换、ANR或其他一些状态变化,框架可能已经放弃了前一个手势的up或cancel事件。cancelAndClearTouchTargets(ev);resetTouchState();}//检查拦截。finalbooleanintercepted;if(actionMasked==MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null){finalbooleandisallowIntercept=(mGroupFlags&FLAG_DISALLOW_INTERCEPT)!=0;if(!disallowIntercept){//TODO1、当返回true,表示该事件被当前视图拦截;当返回false,继续执行事件分发。intercepted=onInterceptTouchEvent(ev);ev.setAction(action);//恢复操作,以防它被更改}else{intercepted=false;}}else{//没有触摸目标,这个动作不是初始向下,所以这个ViewGroup继续拦截触摸。intercepted=true;}//如果被拦截,启动正常的事件分发。另外,如果已经有一个处理手势的视图,则执行正常的事件分派。if(intercepted||mFirstTouchTarget!=null){ev.setTargetAccessibilityFocus(false);}//检查取消。finalbooleancanceled=resetCancelNextUpFlag(this)||actionMasked==MotionEvent.ACTION_CANCEL;//如果需要,更新指针指向的触摸目标列表。finalbooleansplit=(mGroupFlags&FLAG_SPLIT_MOTION_EVENTS)!=0;TouchTargetnewTouchTarget=null;booleanalreadyDispatchedToNewTouchTarget=false;if(!canceled&&!intercepted){//如果事件的目标是可访问性焦点,那么我们将它交给具有可访问性焦点的视图,如果它不处理它,我们将清除标志并像往常一样将事件分派给所有的孩子。//我们正在查找可访问性集中的主机,以避免保持状态,因为这些事件非常罕见。ViewchildWithAccessibilityFocus=ev.isTargetAccessibilityFocus()?findChildWithAccessibilityFocus():null;if(actionMasked==MotionEvent.ACTION_DOWN||(split&&actionMasked==MotionEvent.ACTION_POINTER_DOWN)||actionMasked==MotionEvent.ACTION_HOVER_MOVE){finalintactionIndex=ev.getActionIndex();//always0fordownfinalintidBitsToAssign=split?1<
/***实现此方法来拦截所有触摸屏动作事件。这允许您在事件发送给孩子时查看它们,并在任何时候获得当前动作的所有权。*使用这个方法需要谨慎,因为它与View.onTouchEvent(MotionEvent)的交互相当复杂,使用它需要以正确的方式实现这两个方法。事件将按以下顺序接收:*1、您将在这里接收down事件。*2、down事件将由这个视图组的一个子视图组处理,或者交给您自己的onTouchEvent()方法来处理;这意味着您应该实现onTouchEvent()来返回true,这样您将继续看到手势的其余部分(而不是寻找父视图来处理它)。另外,通过从onTouchEvent()返回true,您将不会在onInterceptTouchEvent()中收到任何后续事件,并且所有的触摸处理都必须在onTouchEvent()中正常发生。*3、只要从这个函数返回false,接下来的每个事件(直到并包括最终的up)都将首先在这里传递,然后传递到目标的onTouchEvent()。*4、如果您从这里返回true,您将不会接收到任何以下事件:目标视图将接收到相同的事件,但是带有actionMotionEvent#ACTION_CANCEL,并且所有进一步的事件将被传递到您的onTouchEvent()方法,并且不再出现在这里。**@paramev要沿层次结构向下分派的触摸事件。*@return返回true从子元素中窃取动作事件,并通过onTouchEvent()将它们分配给这个ViewGroup。当前目标将接收一个ACTION_CANCEL事件,这里不再传递任何消息。*/publicbooleanonInterceptTouchEvent(MotionEventev){if(ev.isFromSource(InputDevice.SOURCE_MOUSE)&&ev.getAction()==MotionEvent.ACTION_DOWN&&ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)&&isOnScrollbarThumb(ev.getX(),ev.getY())){returntrue;}returnfalse;}ViewGroup.buildTouchDispatchChildList()ViewGroup.java
/***提供视图的自定义顺序,在其中分配触摸。这是在紧密循环中调用的,因此不允许分配对象,包括返回数组。相反,您应该返回一个预先分配的列表,该列表将在分派完成后被清除。*@hide*/publicArrayList
/***填充(并返回)mPreSortedChildren一个视图的子元素的预定列表,首先按Z排序,然后按子元素绘制顺序排序(如果适用的话)。此列表在使用后必须清除,以避免泄漏子视图。使用一个稳定的插入排序,通常是O(n)的viewgroup很少上升的孩子。*/ArrayList
/***将触摸事件转换为特定子视图的坐标空间,筛选不相关的指针id,并在必要时覆盖其操作。如果child为空,则假设MotionEvent将被发送到这个ViewGroup。*/privatebooleandispatchTransformedTouchEvent(MotionEventevent,booleancancel,Viewchild,intdesiredPointerIdBits){finalbooleanhandled;//取消触摸是一种特殊情况。我们不需要执行任何转换或过滤。重要的是行动,而不是内容。finalintoldAction=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);returnhandled;}//计算要传递的指针的数量。finalintoldPointerIdBits=event.getPointerIdBits();finalintnewPointerIdBits=oldPointerIdBits&desiredPointerIdBits;//如果由于某种原因,我们最终处于不一致的状态,看起来我们可能会产生一个没有指针的触摸事件,那么就删除该事件。if(newPointerIdBits==0){returnfalse;}//如果指针的数量是相同的,并且我们不需要执行任何复杂的不可逆转换,那么我们可以为这个分派重用motion事件,只要我们小心地还原我们所做的任何更改。否则我们需要复印一份。finalMotionEventtransformedEvent;if(newPointerIdBits==oldPointerIdBits){if(child==null||child.hasIdentityMatrix()){if(child==null){//TODO*分发触摸事件handled=super.dispatchTouchEvent(event);}else{finalfloatoffsetX=mScrollX-child.mLeft;finalfloatoffsetY=mScrollY-child.mTop;event.offsetLocation(offsetX,offsetY);//TODO*分发触摸事件handled=child.dispatchTouchEvent(event);event.offsetLocation(-offsetX,-offsetY);}returnhandled;}transformedEvent=MotionEvent.obtain(event);}else{transformedEvent=event.split(newPointerIdBits);}//执行任何必要的转换和分派。if(child==null){//TODO*分发触摸事件handled=super.dispatchTouchEvent(transformedEvent);}else{finalfloatoffsetX=mScrollX-child.mLeft;finalfloatoffsetY=mScrollY-child.mTop;transformedEvent.offsetLocation(offsetX,offsetY);if(!child.hasIdentityMatrix()){transformedEvent.transform(child.getInverseMatrix());}//TODO*分发触摸事件handled=child.dispatchTouchEvent(transformedEvent);}//结束transformedEvent.recycle();returnhandled;}ViewGroup.addTouchTarget()ViewGroup.java
/***将指定子对象的触摸目标添加到列表的开头。假设目标子节点不存在。*/privateTouchTargetaddTouchTarget(@NonNullViewchild,intpointerIdBits){finalTouchTargettarget=TouchTarget.obtain(child,pointerIdBits);target.next=mFirstTouchTarget;mFirstTouchTarget=target;returntarget;}2、View对触摸事件的分发流程1、先由OnTouchListener的OnTouch()来处理事件,当返回true,则消费该事件,否则由onTouchEvent处理事件;2、当onTouchEvent返回true时,消费该事件,否则继续分发该事件。
View.java
/***将触摸屏触摸事件向下传递到目标视图,如果是目标视图,则传递到该视图。*@paramevent要分发的触摸事件。*@return如果事件是由视图处理的,则为真,否则为假。*/publicbooleandispatchTouchEvent(MotionEventevent){booleanresult=false;finalintactionMasked=event.getActionMasked();if(actionMasked==MotionEvent.ACTION_DOWN){//在Down事件之前,如果存在滚动操作则停止。不存在则不进行操作stopNestedScroll();}//mOnTouchListener.onTouch优先于onTouchEvent。if(onFilterTouchEventForSecurity(event)){//当存在OnTouchListener,且视图状态为ENABLED时,调用onTouch()方法ListenerInfoli=mListenerInfo;if(li!=null&&li.mOnTouchListener!=null&&(mViewFlags&ENABLED_MASK)==ENABLED&&li.mOnTouchListener.onTouch(this,event)){result=true;//如果已经消费事件,则返回True}//如果OnTouch()没有消费Touch事件则调用OnTouchEvent()if(!result&&onTouchEvent(event)){//[见小节2.5.1]result=true;//如果已经消费事件,则返回True}}if(!result&&mInputEventConsistencyVerifier!=null){mInputEventConsistencyVerifier.onUnhandledEvent(event,0);}//处理取消或抬起操作if(actionMasked==MotionEvent.ACTION_UP||actionMasked==MotionEvent.ACTION_CANCEL||(actionMasked==MotionEvent.ACTION_DOWN&&!result)){stopNestedScroll();}returnresult;}View.onTouchEvent()View.java
publicbooleanonTouchEvent(MotionEventevent){finalfloatx=event.getX();finalfloaty=event.getY();finalintviewFlags=mViewFlags;//当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件if((viewFlags&ENABLED_MASK)==DISABLED){if(event.getAction()==MotionEvent.ACTION_UP&&(mPrivateFlags&PFLAG_PRESSED)!=0){setPressed(false);}return(((viewFlags&CLICKABLE)==CLICKABLE||(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE));}if(mTouchDelegate!=null){if(mTouchDelegate.onTouchEvent(event)){returntrue;}}//当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;//与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.if(((viewFlags&CLICKABLE)==CLICKABLE||(viewFlags&LONG_CLICKABLE)==LONG_CLICKABLE)){switch(event.getAction()){caseMotionEvent.ACTION_UP:booleanprepressed=(mPrivateFlags&PFLAG_PREPRESSED)!=0;if((mPrivateFlags&PFLAG_PRESSED)!=0||prepressed){booleanfocusTaken=false;if(isFocusable()&&isFocusableInTouchMode()&&!isFocused()){focusTaken=requestFocus();}if(prepressed){setPressed(true,x,y);}if(!mHasPerformedLongPress){//这是Tap操作,移除长按回调方法removeLongPressCallback();if(!focusTaken){if(mPerformClick==null){mPerformClick=newPerformClick();}//调用View.OnClickListenerif(!post(mPerformClick)){performClick();}}}if(mUnsetPressedState==null){mUnsetPressedState=newUnsetPressedState();}if(prepressed){postDelayed(mUnsetPressedState,ViewConfiguration.getPressedStateDuration());}elseif(!post(mUnsetPressedState)){mUnsetPressedState.run();}removeTapCallback();}break;caseMotionEvent.ACTION_DOWN:mHasPerformedLongPress=false;if(performButtonActionOnTouchDown(event)){break;}//获取是否处于可滚动的视图内booleanisInScrollingContainer=isInScrollingContainer();if(isInScrollingContainer){mPrivateFlags|=PFLAG_PREPRESSED;if(mPendingCheckForTap==null){mPendingCheckForTap=newCheckForTap();}mPendingCheckForTap.x=event.getX();mPendingCheckForTap.y=event.getY();//当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态,用来判断用户是否想要滚动。默认延时为100mspostDelayed(mPendingCheckForTap,ViewConfiguration.getTapTimeout());}else{//当不再滚动视图内,则立刻反馈按压状态setPressed(true,x,y);checkForLongClick(0);//检测是否是长按}break;caseMotionEvent.ACTION_CANCEL:setPressed(false);removeTapCallback();removeLongPressCallback();break;caseMotionEvent.ACTION_MOVE:drawableHotspotChanged(x,y);if(!pointInView(x,y,mTouchSlop)){removeTapCallback();if((mPrivateFlags&PFLAG_PRESSED)!=0){removeLongPressCallback();setPressed(false);}}break;}returntrue;}returnfalse;}总结方法
返回
解释
Activity.dispatchTouchEvent()
true
消费事件
Activity.dispatchTouchEvent()
false
消费事件
Activity.dispatchTouchEvent()
super
向下分发事件给ViewGroup.dispatchTouchEvent(),如果是DOWN事件,则触发onUserInteraction()
ViewGroup.dispatchTouchEvent()
true
消费事件
ViewGroup.dispatchTouchEvent()
false
向上分发事件给Activity.onTouchEvent()
ViewGroup.dispatchTouchEvent()
super
分发事件给ViewGroup.onInterceptTouchEvent()
Activity.onTouchEvent()
true
消费事件
Activity.onTouchEvent()
false
消费事件
Activity.onTouchEvent()
super
消费事件
ViewGroup.onInterceptTouchEvent()
true
分发事件给ViewGroup.OnTouchListener.onTouch()
ViewGroup.onInterceptTouchEvent()
false
向下分发事件给View.dispatchTouchEvent()
ViewGroup.onInterceptTouchEvent()
super
向下分发事件给View.dispatchTouchEvent()
ViewGroup.OnTouchListener.onTouch()
true
消费事件
ViewGroup.OnTouchListener.onTouch()
false
分发事件给ViewGroup.onTouchEvent()
ViewGroup.onTouchEvent()
true
消费事件
ViewGroup.onTouchEvent()
false
向上分发事件给Activity.onTouchEvent()
ViewGroup.onTouchEvent()
super
向上分发事件给Activity.onTouchEvent()
View.dispatchTouchEvent()
true
消费事件
View.dispatchTouchEvent()
false
向下分发事件给View.OnTouchListener.onTouch()
View.dispatchTouchEvent()
super
向下分发事件给View.OnTouchListener.onTouch()
View.OnTouchListener.onTouch()
true
消费事件
View.OnTouchListener.onTouch()
false
分发事件给View.onTouchEvent()
View.onTouchEvent()
true
消费事件
View.onTouchEvent()
false
向上分发事件给ViewGroup.OnTouchListener.onTouch()
View.onTouchEvent()
super
消费事件
1、onInterceptTouchEvent返回值true表示事件拦截,onTouch/onTouchEvent返回值true表示事件消费。
2、触摸事件先交由Activity.dispatchTouchEvent。再一层层往下分发,当中间的ViewGroup都不拦截时,进入最底层的View后,开始由最底层的OnTouchEvent来处理,如果一直不消费,则最后返回到Activity.OnTouchEvent。
3、ViewGroup才有onInterceptTouchEvent拦截方法。在分发过程中,中间任何一层ViewGroup都可以直接拦截,则不再往下分发,而是交由发生拦截操作的ViewGroup的OnTouchEvent来处理。
4、子View可调用requestDisallowInterceptTouchEvent方法,来设置disallowIntercept=true,从而阻止父ViewGroup的onInterceptTouchEvent拦截操作。
5、OnTouchEvent由下往上冒泡时,当中间任何一层的OnTouchEvent消费该事件,则不再往上传递,表示事件已处理。
6、如果View没有消费ACTION_DOWN事件,则之后的ACTION_MOVE等事件都不会再接收。
7、只要View.onTouchEvent是可点击或可长按,则消费该事件.
8、onTouch优先于onTouchEvent执行,onTouch的位置在onTouchEvent前面。当onTouch返回true,则不执行onTouchEvent,否则会执行onTouchEvent。onTouch只有View设置了OnTouchListener,且是enable的才执行该方法。
程序员专栏
扫码关注填加客服
长按识别下方二维码进群
近期精彩内容推荐:
在看点这里好文分享给更多人↓↓