超级好文:https://www.jianshu.com/p/38015afcdb58
问题一:事件传递的节点代码是啥?即activity如何把事件传递给ViewGrope,ViewGrope如何把事件传递给View?具体代码
1.activity传给ViewGrope:
activity的dispatchTouchEvent()调用getWindow().superDispatchTouchEvent(ev),然后phonewindow的superDispatchTouchEvent调用mDecor.superDispatchTouchEvent(event),decorView的superDispatchTouchEvent调用super.dispatchTouchEvent(event),这个super就是ViewGrope
2.ViewGrop往下传:
如果不拦截,就遍历当前ViewGrop下的所有子View并用frame.contains(scrolledXInt, scrolledYInt)判断当前遍历的view是不是正在点击的view,若是则调用child.dispatchTouchEvent(ev),如果child是ViewGrope则递归如果是View则实现了事件由ViewGrope传递到View的传递
问题二:代码回调的细节?
1.首先View的dispatchTouchEvent如果没有设置 onTouchListener走默认流程会走View的onTouchEvent,
这时会分两种情况:
(1)如果该View消耗了这个事件onTouchEvent会返回true并且回到View的dispatchTouchEvent也返回true,这时回到了ViewGroup的dispatchTouchEvent,关键代码:
// 条件判断的内部调用了该View的dispatchTouchEvent()
// 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
// 调用子View的dispatchTouchEvent后是有返回值的
// 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因
此会导致条件判断成立
// 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
// 即把ViewGroup的点击事件拦截掉,所谓的拦截是不走下面的判定代码了,
子View消耗掉了点击事件,例如父View和子View同时设置click事件如果点击子
View父View是不会相应的。
}
可以看到ViewGroup的dispatchTouchEvent会返回true,然后一直回溯到Phonewindow的superDispatchTouchEvent,然后activity的dispatchTouchEvent也就成了true
(2)如果该View没有消耗这个事件onTouchEvent会返回false,这时View的dispatchTouchEvent会返回false,然后到上面代码ViewGrope的child.dispatchTouchEvent(ev)返回false会继续调用下面的代码,下面的代码调用了ViewGroup父类的dispatchtouchevent即:View.dispatchTouchEvent()因此会执行ViewGroup的onTouch()->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
这时会出现两种情况:1.ViewGrope消耗了这个事件,之后就会像上面(1)分析的那样走流程
2.ViewGrope没有消耗会返回false,然后递归如果其中有一个ViewGrope消耗就会重复走上面(2)的流程,如果所有的ViewGrope都没有消耗事件一直返回false,最终phoneWindow的superDispatchTouchEvent(ev)会返回false,然后就会走activity的onTouchEvent。
// 重点分析3
// 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
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不消耗事件,上面的child.dispatchTouchEvent(ev)返回false,代码会
// 调用ViewGroup父类的dispatchTouchEvent()如上,即View.dispatchTouchEvent()
// 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
// 此处需与上面区别:子View的dispatchTouchEvent()
}
3.在View的dispatchtouchevent中会先判断是否设置了ontouchListener,如果设置了ontouchListener的ontouch方法返回值如果是true就不会执行View的ontouchEvent直接返回true
/**
* 源码分析:View.dispatchTouchEvent()
*/
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
// 说明:只有以下3个条件都为真,dispatchTouchEvent()才返回true;否则执行onTouchEvent()
// 1. mOnTouchListener != null
// 2. (mViewFlags & ENABLED_MASK) == ENABLED
// 3. mOnTouchListener.onTouch(this, event)
// 下面对这3个条件逐个分析
/**
* 条件1:mOnTouchListener != null
* 说明:mOnTouchListener变量在View.setOnTouchListener()方法里赋值
*/
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
// 即只要我们给控件注册了Touch事件,mOnTouchListener就一定被赋值(不为空)
}
/**
* 条件2:(mViewFlags & ENABLED_MASK) == ENABLED
* 说明:
* a. 该条件是判断当前点击的控件是否enable
* b. 由于很多View默认enable,故该条件恒定为true
*/
/**
* 条件3:mOnTouchListener.onTouch(this, event)
* 说明:即 回调控件注册Touch事件时的onTouch();需手动复写设置,具体如下(以按钮Button为例)
*/
button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});
// 若在onTouch()返回true,就会让上述三个条件全部成立,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
// 若在onTouch()返回false,就会使得上述三个条件不全部成立,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
左侧虚线:具备相关性 & 逐层返回
default1:代码顺序会调用到
getWindow().superDispatchTouchEvent(ev)
default2:我个人没有发现这条线,感觉有点像ViewGrope的dispatchTouchevent的false那条线,这个问题遗留一下
4.遇到一个问题:只用手点一下,理论上来说整个事件流程是:actionDown和actionUp,但是在断点跟踪的时候走了actionMove对我的知识体系产生了冲击,后来了解到:
MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。
在以后开发过程中要注意这一点,用touchSlop来判断是否发生了move,不能单用系统是否调用了move事件