传递顺序:
Activity-->phoneWindow-->DecorView-->子View
public boolean dispatchTouchEvent(MotionEvent ev); 用来进行事件分发表示是否消耗当前事件
public boolean onInterceptTouchEvent(MotionEvent ev); 判断是否拦截某个点击事件 返回true就拦截 false不拦截向下传递 ;
public boolean onTouchEvent(MotionEvent ev); 用来处理点击事件
当点击屏幕后会调用ViewGroup的dispatchTouchEvent
在这个方法中会对触摸事件进行判断 类型DOWN MOVE UP
比如是点击事件 会在这个方法中判断当前是否拦截
怎么判断是否拦截onInterceptTouchEvent
true则拦截 false 不拦截
如果拦截则调用ViewGroup的onTouchEvent方法
如果不拦截则遍历子View根据当前点击坐标传递给子View分发给子View;
如何不拦截 子View调用requestDisallowInterceptTouchEvent(boolean)
分发给子View后 子View调用dispatchTouchEvent 会判断mOnTouchListener是否为null
和View是否为enable状态还有mOnTouchListener.onTouch是否为true
这三个条件如果都满足,直接return true 表示事件被消费; 也就是下面的onTouchEvent(event)不会被执行了;
如果onTouchEvnet执行则会在onTouchEvent中判断触摸事件类型DOWN MOVE UP
View的事件分发 和ViewGroup的事件分发
View的事件分发
不管是DOWN,MOVE,UP都会按照下面的顺序执行:
1、dispatchTouchEvent
在该方法中会去判断mOnTouchListener不为null,并且view是enable的状态 OnTouchListener.onTouch(this, event)返回true,这三个条件如果都满足,直接return true ;
2、 setOnTouchListener的onTouch
如果我们设置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不会被执行了
3、onTouchEvent
如果我们的View可以点击或者可以长按,则,注意IF的范围,最终一定return true ;
1、MotionEvent.ACTION_DOWN
当用户按下,首先会设置标识为PREPRESSED
如果115后,没有抬起,会将View的标识设置为PRESSED且去掉PREPRESSED标识,然后发出一个检测长按的延迟任务,延时为:ViewConfiguration.getLongPressTimeout() - delayOffset(500ms -115ms),这个115ms刚好时检测额PREPRESSED时间;也就是用户从DOWN触发开始算起,如果500ms内没有抬起则认为触发了长按事件:
75行:给mPrivateFlags设置一个PREPRESSED的标识
76行:设置mHasPerformedLongPress=false;表示长按事件还未触发;
77行:发送一个延迟为ViewConfiguration.getTapTimeout()的延迟消息,到达延时时间后会执行CheckForTap()里面的run方法:
2、MotionEvent.ACTION_MOVE
拿到当前触摸的x,y坐标;
判断当然触摸点有没有移出我们的View,如果移出了:
1、执行removeTapCallback();
2、然后判断是否包含PRESSED标识,如果包含,移除长按的检查:removeLongPressCallback();
3、最后把mPrivateFlags中PRESSED标识去除,刷新背景;
3、MotionEvent.ACTION_UP
总结
1、整个View的事件转发流程是:
View.dispatchEvent->View.setOnTouchListener->View.onTouchEvent
在dispatchTouchEvent中会进行OnTouchListener的判断,如果OnTouchListener不为null且返回true,则表示事件被消费,onTouchEvent不会被执行;否则执行onTouchEvent。
DOWN时:
a、首先设置标志为PREPRESSED,设置mHasPerformedLongPress=false ;然后发出一个115ms后的mPendingCheckForTap;
b、如果115ms内没有触发UP,则将标志置为PRESSED,清除PREPRESSED标志,同时发出一个延时为500-115ms的,检测长按任务消息;
c、如果500ms内(从DOWN触发开始算),则会触发LongClickListener:
此时如果LongClickListener不为null,则会执行回调,同时如果LongClickListener.onClick返回true,才把mHasPerformedLongPress设置为true;否则mHasPerformedLongPress依然为false;
MOVE时:
主要就是检测用户是否划出控件,如果划出了:
115ms内,直接移除mPendingCheckForTap;
115ms后,则将标志中的PRESSED去除,同时移除长按的检查:removeLongPressCallback();
UP时:
a、如果115ms内,触发UP,此时标志为PREPRESSED,则执行UnsetPressedState,setPressed(false);会把setPress转发下去,可以在View中复写dispatchSetPressed方法接收;
b、如果是115ms-500ms间,即长按还未发生,则首先移除长按检测,执行onClick回调;
c、如果是500ms以后,那么有两种情况;
i.设置了onLongClickListener,且onLongClickListener.onClick返回true,则点击事件OnClick事件无法触发;
ii.没有设置onLongClickListener或者onLongClickListener.onClick返回false,则点击事件OnClick事件依然可以触发;
d、最后执行mUnsetPressedState.run(),将setPressed传递下去,然后将PRESSED标识去除;
ViewGroup的事件分发
可以看到大体的事件流程为:
MyLinearLayout的dispatchTouchEvent -> MyLinearLayout的onInterceptTouchEvent -> MyButton的dispatchTouchEvent ->Mybutton的onTouchEvent
可以看出,在View上触发事件,最先捕获到事件的为View所在的ViewGroup,然后才会到View自身~~
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代码中了
总结一下
onInterceptTouchEvent 是ViewGroup提供的方法,返回false不拦截,返回true表示拦截 默认false。
ViewGroup的dispatchTouchEvent是真正在执行“分发”工作,而View的dispatchTouchEvent方法,并不执行分发工作,或者说它分发的对象就是自己,决定是否把touch事件交给自己处理,而处理的方法,便是onTouchEvent事件
ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。
假如我们在某个ViewGroup的onInterceptTouchEvent中,将Action为Down的Touch事件返回true,那便表示将该ViewGroup的所有下发操作拦截掉
onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。
ViewGroup实现捕获到DOWN事件,如果代码中不做TOUCH事件拦截,则开始查找当前x,y是否在某个子View的区域内,如果在,则把事件分发下去。
如果没有被设置那么在onTouchEvent中如果设置了mOnClickListener 则onClick会被调用。如果顶级viewGroup不拦截事件则事件会被传递到事件链的子view这时候子view的dispatchTouchEvent会被调用到此为止 事件已经从顶级view传递给了下一级view 接下来的传递过程和顶级view是一致的如此循环完成整个事件的分发。
如何拦截?
复写ViewGroup的onInterceptTouchEvent方法:
默认是不拦截的,即返回false 如果你需要拦截,只要return true这样该事件就不会往子View传递了