需要搞懂的疑问
- 当按住一个在Linearlayout里的button后滑动出这个button,为什么这个button还能继续接收处理触摸事件
- 触摸事件是如何传递给Activity,才继续进行Activity->Window->View的分发的
- 事件分发递归调用的流程整理
- 上层ViewGroup设置onTouchListener并且在onTouch方法返回true,为什么它的子view还是可以接收到触摸事件
- 了解为什么设置setEnable(false)后,onTouchEvent()事件还可以发生,onclick事件为什么没有
- 当touch事件继续传递给一个坐标不在该view内的view对象时,它是怎么处理的
- 当一个viewGroup有并列的子view时,是先把事件传递给哪个子view的
- MotionEvent里的x,y坐标都是相对于谁的坐标值
事件源码分析
简化ViewGroup的dispatchTouchEvent()方法如下:
//子view可设置该变量为true禁止拦截touch事件
private boolean isDisallowIntercept;
public boolean dispatchTouchEvent(MotionEvent ev) {
//********预先定义处理结果变量********
boolean handled = false;
//重置标记位和变量
if(actionMasked == MotionEvent.ACTION_DOWN){
mFirstTouchTarget = null; //mFirstTouchTarget设置为null,并且清空单链表
isDisallowIntercept = false; //重置失效拦截标志位,所以ACTION_DOWN的时,父view想拦截是一定可以拦截的
}
//********检查是否拦截********
final boolean intercepted;
//如果ACTION_DOWN的时候拦截了事件,则mFirstTouchTarget=null,后续的ACTION_MOVE和ACTION_UP事件
// 则不会进入这个判断,onInterceptTouchEvent也就没有再执行的机会和必要了
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
if (!isDisallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
//********判断分发********
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) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
//如果之前该child已经处理过该序列事件中的事件,找到后直接返回
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
break;
}
//调用child的dispatchTouchEvent()返回,如果返回true,则创建新的TouchTarget并加在链表头
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//如果遍历中没找到处理的子view,并且单链表不为null,则newTouchTarget指向链表尾部元素
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
}
}
}
//********处理分发结果********
if (mFirstTouchTarget == null) {
// 没有子view处理,则自己调用自己的处理方法,看自己是否处理
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
//如果是上面处理ACTION_DOWN时新到的处理子view,则直接返回true
if (alreadyDispatchedToNewTouchTarget && newTouchTarget == mFirstTouchTarg
handled = true;
} else {
//可以看到intercepted到后面处理时还是有用的,如果一系列事件开始已经交给某个子view处理
//当处理到中间某一个事件的时候突然拦截
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//如果拦截了某个中间事件,则会给之前处理的子view发送CANCEL事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
//如果是第一次循环,且链表只有一个头元素,则next=null,相当于清空了mFirstTouchTarget
//则下一次事件来的时候intercepted直接为true,不会再传递给子view处理
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
return handled;
}
简化View的dispatchTouchEvent()方法如下:
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//判断OnTouchListener是否不为空,并且viw是否是ENABLED,如果都是则会执行OnTouchListener
//的onTouch()事件。如果的onTouch事件返回true,则直接返回,不会执行view本身的onTouchEvent方法
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//当上面的判断返回false时,才有机会执行view本身的onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
//OnTouchListener的onTouch()和view本身的onTouchEvent只要有一个返回true,
// 则表示该view消费事件
return result;
}
简化View的onTouchEvent()方法如下:
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//如果view的ENABLED为false,则当view的clickable或者longClickable有一个是true的话,则
//消费事件,但是什么都不做,所以onClick()方法也不会触发。否则则直接返回不消费事件
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//如果设置了TouchDelegate类,则调用TouchDelegate的onTouchEvent()方法处理,
//如果TouchDelegate的onTouchEvent()返回true则直接返回消费事件。
//从TouchDelegate类文档看该类用于帮助扩大touch区域
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
//当在PREPRESSED或者PRESSED状态的时候才进入
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//触发OnClickListener的onClick()的方法,用post方式
if (!post(mPerformClick)) {
performClick();
}
}
}
removeTapCallback();
}
break;
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
//设置为PREPRESSED状态
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//检查是否为长按事件
checkForLongClick(0);
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
//移动出view所在的区域
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
removeLongPressCallback();
//当移出view区域时,如果当前为PRESSED状态,则会清除PRESSED状态,并且刷新view
//比如view背景设置为一个selector,则会恢复显示normal状态下的背景
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
小结
由于ViewGroup的onInterceptTouchEvent()方法默认实现为直接返回false和当ViewGroup没找到子view处理touch事件的时候,会调用super.dispatchTouchEvent(event),即view的dispatchTouchEvent()方法,所以从上面的源码可以得出下面结论:
当某一个ViewGroup或者View的dispatchTouchEven()方法返回true的时,则相对于它们的父view表示该ViewGroup或者View消费了这次touch事件。对于ViewGroup有可能是自身的OnTouchListener或onTouchEvent方法消费了,也有可能是子view消费了,而对于View而言,则只能是前者。但对于它们父view来说是没有区别的,父view的mFirstTouchTarget中保存的是该ViewGroup或者View。
整个调用过程有点类递归的感觉,递归的深度则是整个布局中view层次的深度,所以会有往下深入和网上冒出的过程,俗称的往下隧道方式,往上冒泡方式的传递过程。而且这样的过程最多只有ACTION_DOWN的时候能够完整走一遍,当ACTION_MOVE和ACTION_UP的时候,DecorView的mFirstTouchTarget已经记录了消费的直接子view,如果这个直接子view是ViewGroup的话,则它的mFirstTouchTarget又记录了它里面消费事件的子view,如此循环,最后交给最深的处的消费子view,没有了ACTION_DOWN时遍历寻找消费子view的过程。
每一个事件ViewGroup都会遍历mFirstTouchTarget单链表里所有记录的view,如果不是cancelChild则会给他们发送正常事件,如果是则会给该child发送ACTION_CANCEL事件,并且从链表中移除该child。判断一个view是不是cancelChild则通过判断该child的标记位和ViewGroup是否拦截来决定,只有有一个成立,则是cancelChild。不管是发送正常事件还是ACTION_CANCEL事件,都会调用子view的dispatchTouchEven()方法,只要有一个返回true,则ViewGroup会向上返回true表示消费该事件。
当动态改变ViewGroup的onInterceptTouchEvent()方法的返回值时,比如在ACTION_DOWN时返回false不拦截,ACTION_MOVE的时候返回true,则ViewGroup在第一次ACTION_MOVE事件到来时把事件转变为ACTION_CANCEL发送给之前处理事件的子view,并且会把mFirstTouchTarget置为null,下次ACTION_MOVE或ACTION_UP时来到时,intercepted则为true表示拦截,ViewGroup会调用自己的OnTouchListener或onTouchEvent方法。
当动态改变子view的dispatchTouchEven()方法返回值时,比如在ACTION_DOWN时返回true消费事件,ACTION_MOVE的时候返回false,由于父ViewGroup的mFirstTouchTarget单链表里保存的该子view,只有在它是cancelChild的时候才会移除,就算ACTION_MOVE的时候返回false导致子view的dispatchTouchEvent()方法返回false,父ViewGroup的mFirstTouchTarget单链表还是会保存该子view,所有后续的ACTION_MOVE和ACTION_UP事件还是会继续发送给该子view。可以看出子view的dispatchTouchEvent()方法返回值只有在ACTION_DOWN事件时才能起到决定事件走向的作用。但是由于子view的dispatchTouchEvent()方法返回false,而且也不会走父ViewGroup的OnTouchListener或onTouchEvent方法,所以最终到Activity的时,DecorView的dispatchTouchEvent()方法也会返回false,这时候会执行Activity的onTouchEvent()方法。
回答疑问
当按住一个在Linearlayout里的button后滑动出这个button,为什么这个button还能继续接收处理触摸事件
因为Linearlayout的mFirstTouchTarget里面保存有该button,并且判断它不是cancelChild,所以会一直转发事件发给它,button内部的onTouchEvent()方法,ACTION_MOVE时会判断是否滑出button边界,如果是则会清除PRESSED状态,并且刷新背景,当ACTION_UP的时候判断不是PRESSED状态,所以OnClickListener的onclick()方法也不会触发。触摸事件是如何传递给Activity,才继续进行Activity->Window->View的分发的
事件分发递归调用的流程整理
上层ViewGroup设置onTouchListener并且在onTouch方法返回true,为什么它的子view还是可以接收到触摸事件
因为如果子view的dispatchTouchEven()方法返回true的话,则会先发送给子view消费,不会走到ViewGroup的onTouchListener和onTouchEvent()方法。只有当点击ViewGroup中未包含子view的地方才会不经过子view直接自己消费。了解为什么设置setEnable(false)后,onTouchEvent()事件还可以发生,onclick事件为什么没有
因为setEnable(false)后,view的CLICKABLE或者LONG_CLICKABLE还是true的,所以还可以继续消费事件,只是接收后,什么都不做直接返回true,所以onclick事件不会触发当touch事件继续传递给一个坐标不在该view内的view对象时,它是怎么处理的
同第一个问题,ACTION_MOVE时会判断是否滑出button边界,如果是则会清除PRESSED状态,并且刷新背景,当ACTION_UP的时候判断不是PRESSED状态,所以OnClickListener的onclick()方法也不会触发。当一个viewGroup有并列的子view时,是先把事件传递给哪个子view的
preorderedList中的顺序是按照addView或者XML布局文件中的顺序来的,后addView添加的子View,会因为Android的UI后刷新机制显示在上层,所以有覆盖的并列子view时,会先传递给上层view(已验证源码待分析)MotionEvent里的x,y坐标都是相对于谁的坐标值
MotionEvent有四个方法getRawX(), event.getRawY(), getX(),getY()。awX和rawY是相对于屏幕的坐标,x和y是相对于当前控件的坐标。rawX和X 向右移动都是增大,向左移动都是减小,rawY和 Y 向下移动都是增大,向上移动都是减小。