先从事件的传递开始.
一个事件到达界面时, 它的入口是dispatchTouchEvent. 这个方法是视图处理事件的唯一接口, 所有到达视图的事件, 都必须经过这个方法.
简单地说, 系统接收到一个事件, 要丢到一个LinearLayout里面, 怎么办?
直接调这个dispatchTouchEvent, 接收返回的true或者false, 完了.后面的处理就和这个LinearLayout没关系了.
那就有人问了, 那onInterceptTouchEvent, onTouchEvent, onClickListenr, 或者这个LinearLayout里面的Button, 不是还没处理吗, 怎么就完了?
虽然外部只调用dispatchTouchEvent, 但是在这个方法内部, 它自己会根据一系列逻辑调用这些方法.
件在视图之间传递的顺序大概是这样的:
父容器的dispatchTouchEvent-->调用内部容器的dispatchTouchEvent-->调用基本控件的dispatchTouchEvent.
这里的容器就是ViewGroup, 控件就是各种View.
通过这样的传递, 一个复杂的组件对它的上层来说就变得统一了: 我不要去关心你里面有什么乱七八糟的基本控件, 怎么摆放, 怎么处理. 我分分钟几十万上下, 找一个小小的具体的Button我累不累? 反正我就把事件丢给你, 怎么处理你来决定.
同样他的下级也是这个思路, 把事件丢到更下一级, 最终传达到一个Button让它响应处理.
这就是责任链模式.
但是这样又产生一个问题: 如果父容器只能起一个传递的作用, 事件只能由子控件响应, 那我ScrollView怎么滑动? ViewPager怎么切换? 工作不能都压给最底层的员工吧, 总有些事情是我经理得自己干的吧, 比如做报表写工作计划泡前台调戏测试之类的..... 咳咳扯远了.
所以这里就需要有一个判断的方法. 一个事件来了, 我自己审核一下, 这个是我的职责, 那后续的事情我就揽下来, 不往下传了. 否则就继续往下传. 嘿嘿我真是太机智了.
这个判断的方法就是onInterceptTouchEvent.
说到这里, 要区分 "动作" 和 "事件" 的概念了.
动作就是用户的一个完整的操作, 比如一个点击, 一个滑动, 等等.
而事件就是MotionEvent了.
我们知道所有的事件MotionEvent其实都是瞬态的, 一个事件本身只代表这一瞬间是什么样子. 你无法从单个MotionEvent看出用户当前是滑动还是长按, 滑动了多远, 向左还是向右. 一个完整的滑动动作是由不断触发 ACTION_DOWN, ACTION_MOVE x N, ACTION_UP(或者CANCEL)来组成的. 这些ACTION_DOWN, ACTION_MOVE代表事件的类型(getAction()).
那么问题又来了.一个单独的瞬态的ACTION_MOVE, 我怎么知道这个事件是2秒前的那次DOWN还是5秒前的那次DOWN带着的? 我如何判断一个完整的动作处理完没有?
视图是通过getDownTime() 这个属性. 每个动作的一系列事件都有相同的downTime, 也就是DOWN事件的getEventTime这个方法所返回的时间.
所以, 一个完整的动作是由一组MotionEvent组成的, 他们拥有共同的downTime. 当视图接收到具有一个新的downTime的事件时, 它就认为, 之前的动作已经处理完毕了.
这一块基本很少有提到过, 平时也用不太上. 但是很重要.
这个方法的参数虽然是一个事件, 但它实际上拦截的是一个完整的动作. 这就意味着, 如果你在DOWN事件或者某一个MOVE事件返回了true(意味着告诉视图:"我要拦截了, 这个动作我来处理!"), 那之后的所有事件都不会再继续往子视图传递了,直到有一个新的动作开始!
到这里, 我们可以试图解释一下, 为什么当ViewPager放在ScrollView里面时, 左右滑动经常变得很困难的原因.(筒子们可以试着自己动手写一个Demo, 你会发现左右滑动切换会变得比在外面困难很多.)
原因就是ScrollView在接收到MOVE事件时, 会判断前后两次事件的y坐标之差, 超过一定阈值就会认为是上下滑动事件, 然后就无情地通过onInterceptTouchEvent接管掉了.
而且这个阈值特别小......
而我们在左右滑动的时候, 手指很难保证完全水平地动作. 稍微有一点角度就产生了Y值的变化, 然后ScrollView大爷的onInterceptTouchEvent就觉得"卧槽这是上下滑动事件啊劳资不能不管啊!!!!".然后果断返回true.
于是可怜的ViewPager再也没接收到后续的事件, 也就切换不了了.