View的事件分发机制--笔记

传递顺序:

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自身~~

View的事件分发机制--笔记_第1张图片

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传递了

你可能感兴趣的:(View的事件分发机制--笔记)