(1) 所有Touch事件都被封装成了MotionEvent对象,包括Touch的位置、时间、历史记录以及第几个手指(多指触摸)等。
(2) 事件类型分为ACTION_DOWN,ACTION _UP, ACTION_MOVE,ACTION _POINTER_DOWN,ACTION _POINTER_UP, ACTION_CANCEL,
每个事件都是以ACTION_DOWN开始ACTION_UP结束。
(3) 3个主要方法:
dispathcTouchEvent() 分发、传递事件
onInterceptTouchEvent() 拦截事件
onTouchEvent() 处理事件
其中onInterceptTouchEvent()是ViewGroup独有方法;
Activity、View、ViewGroup都有另外两个方法,不过源码中dispathcTouchEvent里执行的内容各有不同。
一般来说,Android的事件分发机制可以简单地理解为:
Activity.dispatchTouchEvent --> ViewGroup.dispatchTouchEvent --> View.dispatchEvent
其中,Activity.dispatchTouchEvent负责找到最外层ViewGroup;然后用这ViewGroup.dispatchTouchEvent来遍历子view,调用子view的dispatchEvent,处理子view的onTouch、onTouchEvent、onClick
我们来看Activity类dispatchTouchEvent()
@Override public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction();//空方法,忽略吧 } /** * getWindow()获取到当前Window对象,表示顶层窗口,管理界面的显示和事件的响应; * Window类是一个抽象类,只有一个子类PhoneWindow; * 每个Activity均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口 */ if (getWindow().superDispatchTouchEvent(ev)) { //开始一层一层往下遍历view的dispatchTouchEvent,看哪个返回了true return true; } return onTouchEvent(ev);//所有子view都返回false,就调用自身的onTouchEvent(没复写的话,默认调用Activity.onTouchEvent) }所以我们找到
PhoneWindow
类,查看它的superDispatchTouchEvent()
方法。
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event);//mDecor就是当前窗口最顶层的DecorView }
DecorView是整个Window界面的最顶层ViewGroup,是FrameLayout的子类,接着看DecorView
中的superDispatchTouchEvent()
方法
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }调用了
super.dispatchTouchEveent()
,而DecorView
的父类是FrameLayout
所以我们找到FrameLayout.dispatchTouchEveent()
.我们看到FrameLayout
中没有重写dispatchTouchEveent()
方法,所以可以肯定它调用的就是FrameLayout
的父类ViewGroup
的dispatchTouchEvent
()。
绕来绕去,Activity.dispatchTouchEvent()其实就是在寻找最外层ViewGroup,让它来给下面的view分发事件。
事件下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件
public boolean dispatchTouchEvent(MotionEvent event) { /** * mOnTouchListener就是你在setOnTouchListener()中传递的listener * mViewFlags & ENABLED_MASK 意思是判断View控件是否enabled * mOnTouchListener.onTouch() 回调你传递的listener的onTouch方法(上面条件都为true才会执行) */ if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; //true表示事件被这个View消费了 } //这个onTouchEvent里有对onClick的处理,所以如果你给上面的onTouch()返回了true,那么onClick方法就不会被执行了 return onTouchEvent(event); }
第一个if判断中,只要设置了onTouchListener,且控件enable,且onTouch返回true,这个dispatchTouchEvent就返回true,
意味着这个控件触发并处理后续action事件(ACITON_UP,ACTION_MOVE等),不再往下分发事件;也不会执行它自己的onTouchEvent()。
我们来看View.onTouchEvent()的源码(简化):
public boolean onTouchEvent(MotionEvent event) { ... if ((viewFlags & ENABLED_MASK) == DISABLED) { if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch events, it just doesn't respond to them. // 一个控件如果是disabled且clickable,仍然可以消费触摸事件,只是不去响应它 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); } //判断控件是不是clickable if(((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) { switch(event.getAction()) { case MotionEvent.ACTION_UP: ... ... if (!mHasPerformedLongPress) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state</span> if(!focusTaken) { // Use a Runnable and post this rather than calling</span> // performClick directly. This lets other visual state</span> // of the view update before click actions start.</span> if (mPerformClick == null { mPerformClick = <span class="hljs-keyword">new</span> PerformClick(); } if (!post(mPerformClick)) { performClick();//控件被点击时,回调控件的onClickListener.onClick() } } } ... break; case MotionEvent.ACITON_DOWN: ... break; case MotionEvent.ACITON_CANCEL: ... break; case MotionEvent.ACTION_MOVE ... break; } return true; //可见,只要控件是clickable,就返回true,从而触发ACITON_U等后续事件 } }
从第二个if语句可以看到,onTouchEvent会判断控件是不是clickable,只要控件是clickable就会进入到这个if判断的内部,然后不管是什么action,到最后都帮你返回true。
这样做有什么好处呢?我们看一看那个switch语句中的ACITON_UP分支,里面有一个performClick(),它专门用来处理控件的点击事件。我们知道,onToucEvent返回true时,dispatchTouchEvent也跟着返回true,从而触发后续的ACITON_UP,使得onTouchEvent会再一次被调用,然后才能进入到switch的ACITON_UP分支去处理该控件的点击事件。如果不帮你返回true,控件的onClick就永远没法被系统调用。
因此,只要控件是clickable的,控件的dispatchTouchEvent就一定返回true,从而消费事件。(提示:Button默认clickable,ImageView默认不是clickable)
PS:Android中只要view和layout调用了setOnClickListener(),那么它就会被设为clickable(参见下面的View.setOnClickListener()源码)
public void setOnClickListener(OnClickListener l) { if (!isClickable()) { setClickable(true); } mOnClickListener = l; }
public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //这里是ACITON_UP事件的处理逻辑,主要是遍历子view,调用子view的dispatchTouchEvent,看哪个返回true就记录它 if (action == MotionEvent.ACTION_DOWN) { if (mMotionTarget != null) { //mMotionTarget用来记录View.dispathcTouchEvent返回true的子view mMotionTarget = null; //重置目标view } //判断有无拦截事件(disallowIntercept指是否允许事件被拦截,默认是false) if (disallowIntercept || !onInterceptTouchEvent(ev)) { //派发action_down的时候执行一次onInterceptTouchEvent ev.setAction(MotionEvent.ACTION_DOWN); final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View[] children = mChildren; final int count = mChildrenCount; //子view的数量 for (int i = count - 1; i >= 0; i--) { //遍历当前ViewGroup的所有子view final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { child.getHitRect(frame); if (frame.contains(scrolledXInt, scrolledYInt)) { //通过坐标判断用户触摸的区域都有哪些控件 final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; //dispatchTouchEvent的返回值的意义体现在此! if (child.dispatchTouchEvent(ev)) { 若child是个ViewGroup,正好递归调用ViewGroup.dispatchTouchEvent mMotionTarget = child; //记住dispatchTouchEvent返回true的子view return true; //跳出循环,也就是不继续往下调用child.dispatchTouchEvent } } } } } } //判断是否为ACTION_UP或者ACTION_CANCEL</span><span></span></span> boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } final View target = mMotionTarget; //将刚刚记录的子view赋值给target(最后一行,调用它的dispatchTouchEvent) //target为null,即上面的mMotionTarget为null,意味着这个ViewGroup的子view都没消费事件 if (target == null) { ev.setLocation(xf, yf); if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; } return super.dispatchTouchEvent(ev); //若子view都不消费事件,就调用这ViewGroup的父类方法,即View.dispatchTouchEvent } if (!disallowIntercept && onInterceptTouchEvent(ev)) { //再一次执行onInterceptTouchEvent final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; ev.setAction(MotionEvent.ACTION_CANCEL); ev.setLocation(xc, yc); if (!target.dispatchTouchEvent(ev)) { } mMotionTarget = null; return true; } if (isUpOrCancel) { mMotionTarget = null; } final float xc = scrolledXFloat - (float) target.mLeft; final float yc = scrolledYFloat - (float) target.mTop; ev.setLocation(xc, yc); if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) { ev.setAction(MotionEvent.ACTION_CANCEL); target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; mMotionTarget = null; } return target.dispatchTouchEvent(ev); //调用记录的子viwe的dispatchTouchEvent }
根据上面对源码的分析,可知ViewGroup.diaptchTouchEvent逻辑是这样的:
用户触摸屏幕,首先产生的是ACTION_DOWN事件,if判断到当前是down事件,那就遍历ViewGroup中被触摸到的那些子ViewGoup和子View,调用它们的dispatchTouchEvent(如果是子ViewGroup,那不正好是递归嘛,想一想,这样最后肯定能把down事件传递给触摸到的所有viewGroup和view的dispatchTouchEvent,除非其中的某个ViewGroup使用onInterceptTouchEvent把事件拦截掉)。
例子一:button的dispatchTouchEvent返回了true
(什么时候返回true?控件setOnClickListener;或者setOnTouchListener,并在onTouch返回true且控件enable)
派发顺序:
分析:
1. 由于view消费了down事件,它就被记录了,下次传递move和up事件就找它了;
2. 包裹这个view的ViewGroup的onTouch和onTouchEvent将无法被执行
例子二: 某个ViewGroup的onTouchEvent返回true
派发顺序:
分析:
1. 问:为什么down事件派发会走①-②这么个顺序?
答:派发down事件,走到SecondLayout这一ViewGroup的dispatchTouchEvent时,会遍历SecondLayout的子view,发现都是返回false。所以这个ViewGroup.dispatchTouchEvent会继续走到执行后面代码,调用View.dispatchTouchEvent,而View.dispatchTouchEvent调用ViewGroup自身的onTouchEvent(没有复写的话从父类View继承)。这顺序正好对应箭头①。
同理,FirstLayout会等遍历完子view(SecondLayout和ThirdButton),发现它们的dispatchTouchEvent和onTouchEvent都返回false以后,执行后面代码,调用
View.dispatchTouchEvent,ViewGroup.onTouchEvent调用自身onTouchEvent,这个onTouchEvent被我们复写了,并返回true。这顺序对应箭头②
2. 问:黄色路线是怎么回事?
答:派发down事件,其中的某个ViewGroup被记录了,派发后续事件,直接找到它,在ViewGroup.dispatchTouchEvent中直接调用自身的onTouchEvent。
例子三: 全返回false
派发顺序:
如果Activity里的view全都返回false,那么就调用它自身的onTouchEvent(再回到开头看看Activity.dispatchTouchEvent源码,一目了然)。
问:为什么Activity.onTouchEvent无论返回true还是false,都能消费事件?
答:因为Activity.dispatchTouchEvent必然至少被系统调用2次(down和up)
例子四:onInterceptTouchEvent返回true
派发顺序:
onInterceptTouchEvent返回true表示拦截事件,此时,ViewGroup.dispatchTouchEvent中的target为null,调用super.dispatchTouchEvent,即View.dispatchTouchEvent