1.事件基础知识
所谓事件,就是用户手触摸到屏幕时,产生的一系列 Touch 事件,无论是用户的拖拽,点击,还是多点多点放大,缩小等操作,都与事件相关。我们此次只讨论单指点击,移动的情况。
MotionEvent事件类型
事件类型 | 动作 | 代码值 |
---|---|---|
MotionEvent.ACTION_DOWN | 手指按下屏幕(view,所有事件的开始) | 0 |
MotionEvent.ACTION_MOVE | 在屏幕上滑动,会多次触发 | 2 |
MotionEvent.ACTION_UP | 手指抬起(与Down相对应) | 1 |
MotionEvent.ACTION_CANCEL | 结束事件,事件被上层拦截时触发 | 3 |
事件传递涉及方法
方法名 | 作用 | 拥有者 | 返回值 | 执行顺序 |
---|---|---|---|---|
dispatchTouchEvent | 对事件进行分发 | Activity, ViewGroup, View都有此方法 | true:分发;false:不进行分发,其值由子viewa或孙子view的onTouchEvent等决定 | 1 |
onInterceptTouchEvent | 是否拦截此事件,拦截后交由自己的onTouchEvent处理 | (只有ViewGroup有) | true:进行拦截,拦截后,事件交由自己处理;false:不进行拦截 | 2 |
onTouchEvent | 对事件的处理 | Activity, ViewGroup, View都有此方法 | true:事件已消费;false:事件未消费 | 3 |
2.事件是如何传递的
1)事件的开始
所有的事件,都会按照由Activity-->ViewGroup-->View的顺序进行分发,所以,事件开始的入口,便是Activity.dispatchTouchEvent()方法。通过 Activity 源码
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
通过源码可以发现,最终交给了 getWindow() 的实现类进行了事件的处理,而 getWindow() 实际上就是 PhoneWindow ,所以,我们进入 PhoneWindow的代码看一下:
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
而了解过源码的人知道,mDecor 其实是FrameLayout的子类,这也是我们平时说的界面的根布局是一个 FrameLayout的原因,而FrameLayout又是ViewGroup的子类,ViewGroup又是View的子类,所以,默认情况下,我们研究事件的分发,最终是在看 ViewGroup以及View中对上述提到的三个方法的处理
2)事件传递的过程

如图,当Down事件过来后,activity.dispatchTouchEvent,不会进行任何的拦截,就会交由他的child ViewGroup1来处理,即调用ViewGroup1.dispatchTouchEvent方法,该方法内部,会调用ViewGroup1.onInterceptTouchEvent来判断ViewGroup1是否对此事件进行拦截,如果拦截,将交由自己的onTouchEvent来处理,如果不拦截,会将事件交由自己的child ViewGroup2来处理。如此递归循环,一直到最内层的一个view,并将结果逐步返回。onTouchEvent,用来处理点击事件,若消耗了此事件,则之后的事件会接着收到,若不消耗,则在同一事件序列中,此View无法接收到事件。同时,此事件会交由它的父类的onTouchEvent来处理,如果父类不处理,则继续向上抛,直到Activity.onTouchEvent().
伪代码表示:
public boolean dispatchTouchEvent(MotionEvent ev){
boolean consume = false;
if(onInterceptTouchEvent(ev)){
consume = onTouchEvent(ev);
}else{
consume = child.dispatchTouchEvent(ev);
}
return consume;
}
3)事件传递图解

注意:
在dispatch,onIntercept,onTouchEvent三个方法并不是单独存在的,尤其是后两个方法,它他是在dispatch中被调用的,默认情况下,最内层View.onTouchEvent的返回值会影响到View.dispatchTouchEvent的返回值,而该值又回影响到其父类View的返回值
3.源码分析

1)点击View1,Down事件如何传递
首先,Down会经过Activity,DectorView的dispatchTouchEvent,传递到ViewGroup1.dispatchTouchEvent中.因此,我们直接进入源码ViewGroup的该方法中查看
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;
}
}
disallowIntercept==false ---> onInterceptTouchEvent ---> intercepted==false
接下来会走到
if (!canceled && !intercepted) {
if (newTouchTarget == null && childrenCount != 0) {
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
**代码省略**
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
当ViewGroup1不进行拦截时,会遍历子View,通过dispatchTransformedTouchEvent方法将事件传递给子View,下面看一下该方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
***省略***
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
}
由于child不为空,所以在此方法中,会形成递归,继续传递事件,一直到最内存的view为止,因此,事件最终会交由View的dispatchTouchEvent来处理。下面看一下View的disptchTouchEvent源码
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
在View的方法中,会根据onTouchEventListener以及onTouchEvent的处理,返回结果,最终此结果会回到ViewGroup if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) 这里,如果有子view消费了此事件,就会进入if判断,从代码上可以看到,newTouchTarget,mFirstTouchTarget会赋值,而alreadyDispatchedToNewTouchTarget = true;接下来看ViewGroup之后的代码
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
由于mFirstTouchTarget已有值,所以会走到else,在这里面,会命中代码if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
},然后dispatchTouchEvent会返回true
以上便是Down事件,在父View不拦截的情况下的处理流程,如果是拦截的话,我们看一下代码执行。
如果是拦截的话 if (!canceled && !intercepted)这个不成立,就不会有遍历子view的情况,那么 mFirstTouchTarget便是null, 因而会走到
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
此方法,最终会交给super.dispatchTouchEvent,即view对该方法的实现。这也是我们平时说的,当子view不处理事件时,会交给父view,如果父view也不处理,就会再交给爷爷view处理
Down事件总结
dispatchTouchEvent确定事件给谁
1.先看自己是否拦截,如果不拦截:
2.分发下去:排序、遍历、领取事件的View处理事件结果
3.如果子view没人处理事件,则交由自己的onTouchEvent,看自己是否消费此事件
4.如果拦截,其后续流程和没有子view消耗该事件是一样的
2)点击View1后,Move事件如何传递
首先明确一点,Move事件,依然后经Dector-->ViewGroup-->View的顺序,通过dispatchTouchEvent进行传递
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
注意,当move事件过来时,alreadyDispatchedToNewTouchTarget会置为false,同时,上面代码中的if,由于是MOVE事件,if中的代码将不再执行。
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
由于mFristTouchTarget!=null且alreadyDispatchedToNewTouchTarget==false,故move事件最终给了else中的dispatchTransformedTouchEvent处理,因此,move事件,一层层传入了之前保存的 target.child中,并返回处理结果 true
Move事件拦截问题
Move事件的传递相对来说就上是面的流程了,还有一点需要注意的是Move事件的拦截问题,看代码
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;
}
}
在ViewGroup.dispatchTouchEvent方法中,有这样的代码,MotionEvent.ACTION_DOWN || mFirstTouchTarget,意味着,除了Down事件,当move事件时,由于 mFirstTouchTarget!=null,也会走是否进行拦截的判断,而此时需要依赖mGroupFlags & FLAG_DISALLOW_INTERCEPT的结果,这两个参数在ViewGroup.requestDisallowInterceptTouchEvent中会被更改,也就是说,如果子View拿到父ViewGroup,便可调用此方法,在Move事件时,更改父View是否可以拦截事件。如果是这样处理,那以下代码的逻辑便又会有变化
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
此时的move事件,由于intercepted=true,故cancelChild = true,那么再看dispatchTransformedTouchEvent中的处理
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);
return handled;
}
此时,由于cancel==true,所以该事件的会被改为ACTION_CANCEL,进而调用View.dispatchTouchEvent,查看View的该方法,可看到结果返回false,同时,在ViewGroup中,会走以下的逻辑
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
从之前的代码中,可以看到 mFirstrTouchTarget.next==null,因此,此逻辑走完后, mFirstTouchTarget==null, target==null,当下一次Move事件到来后,在是否进行拦截的判断actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null这里,无法进行,就会走 else逻辑:intercepted = true;, 对事件进行拦截,然后执行以下代码逻辑
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}
即随后的move事件将都由该ViewGroup进行处理。
Move事件总结
dispatchTouchEvent确定事件给谁
1.先看自己是否拦截,如果不拦截:
2.分发下去:直接分发给down事件确定的view处理
3.网上说的Down事件如果没接收到,则再也不能接到任何事件,这样的说法是有问题的,这样的说法只适合叶子结点,如果是非页子结点,子View可以通过requestDisallowInterceptTouchEvent来设置父ViewGroup是否对事件进行拦截
3)dispatchTouchEvent,onclick, onTouchListener的关系
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
以上为View.dispatchTouchEvent方法中的代码,从上面可以看到,当给View添加了onTouchListener后,事件到来后,会先响应 mOnTouchListener.onTouch方法,如果该方法返回true,则下面的 if中的onTouchEvent(event)就不会执行,否则,会运行onTouchEvent。那再看一下onTouchEvent方法
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
该方法中,重点为当事件为UP时,会有performClick的处理,再看performClick方法
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
return result;
}
可以看到,在该方法中,会判断view是否有clickListener,然后进行调用,并且有返回值,这也是为什么我们重写clickListener时,方法中不用写返回值,而onTouchListener则需要
Click, TouchEvent,TouchListener总结
View.dispatchTouchEvent中,会先对onTouchListener的设置进行其处理,再根据其结果,判断是否调用view的onTouchEvent方法
在View.onTouchEvent中,当事件为UP时,进行clickListener的调用,且有返回值
给view同时添加onTouchListener和onClickListener时,onTouchListener方法的返回值需为true,否则onClickListener也会响应。
4.发散功能,Spannable
TextView可以添加Spannable,当为textView添加了clickListener及spannalbe后,点击spannable时,click也会响应。
网友评论