注:以下存在伪代码示意事件分发流程,并非源码
Activity分发事件的逻辑:
public boolean dispatchTouchEvent(MotionEvent ev) { //直接将ev委托给Window处理,它的返回值意思是事件有没有被处理 if(getWindow().superDispatchTouchEvent(ev)) { //表示在该Activity中有View处理了该事件 return true; } //没有View愿意处理该事件,调用自身的onTouchEvent方法 return onTouchEvent(ev); }Window分发事件:
public boolean superDispatchTouchEvent(MotionEvent ev) { //Window又直接把ev委托给decorView处理 return mDecor.superDispatchTouchEvent(ev); }ViewGroup与事件分发相关的方法主要有三个:
public boolean dispatchTouchEvent(MotionEvent ev) //用来控制事件的分发 public boolean onInterceptTouchEvent(MotionEvent ev) //用来判断是否拦截事件 public boolean onTouchEvent(MotionEvent ev) //ViewGroup处理事件还有三个重要的标记为来控制事件分发的整体流程:
//是当前ViewGroup的一个子View,这个View消费了Down事件就会被赋值给这个变量 private TouchTargetmFirstTouchTarget //这个标记为通过requestDisallowInterceptTouchEvent方法设置 //该标记可以禁止ViewGroup拦截Move和Up事件,强制将这些事件分发给其子View,在滑动冲突的内部拦截中会用到 final static int FLAG_DISALLOW_INTERCEPT三个方法的调用关系:(伪代码)
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consumed = false; // 默认事件是没有被消费的 resetStatus() //(1) 如果是DOWN事件,需要重置标记位 if(shouldIntercepted(ev)){ //(2) 如果需要拦截,就不下发事件,直接调用onTouchEvent自己处理该事件 consumed = onTouchEvent(ev) }else { consumed = dispatchToChild(ev); //(3) 如果不需要拦截,下发事件 } return consumed; }(1)清除状态
public void resetStatus(MotionEvent ev) { //DOWN事件说明重新开启了一个事件序列,重置标记位 if(ev.getAction == MotionEvent.ACTION_DOWN) { //清除TouchTargetmFirstTouchTarget cancelAndClearTouchTargets(ev); //清除FLAG_DISALLOW_INTERCEPT标记 //所以requestDisallowInterceptTouchEvent不能影响Down事件拦截判断 resetTouchState(); } }(2)判断是否需要拦截事件
public boolean shouldIntercepted(MotionEvent ev) { final boolean intercepted; if(action == MotionEvent.ACITON_DOWN || mFristTouchTarget != null) { boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if(!disallowIntercept){ //disallowIntercept在检查到是DOWN事件时已经被重置为false了,所以DOWN事件一定去会检查是否拦截 intercepted = onInterceptTouchEvent(ev); } else { intercepted = false; } } else { //不是DOWN事件,也没有子View消费过Down事件,就直接拦截这个事件自己处理 //实际上如果一个DOWN事件被当前ViewGroup拦截了,mFristTouchTarget一定为null,也会直接走这个分支 intercepted = true; } }总结: 1.ViewGroup的onInterceptTouchEvent方法不是每个事件到来都会调用: 2.即使onInterceptTouchEvent不拦截事件,最后事件也可能被拦截,因为子View都不消费这个事件
(3)分发事件给子View: 遍历子View获取点击到的并触发其dispatchTouchEvent方法
public boolean dispatchToChild(MotionEvent ev) { for(View child: children) { //遍历所有子View if(isTouched(child)) { //如果触碰到了这个View boolean consumed = child.dispatchTouchEvent(ev); //就把这个事件分发给这个View处理 if(consumed) mFristTouchTarget = child; //这个子View消费了事件,就把mFristTouchTarget 赋值 return consumed; } } return false; }总结: 事件处理优先级:onTouchListener(手动设定) > onTouchEvent(系统自带) > onClickListener(手动设定)
参考:Android开发艺术探索