关于 View 传递事件的博文很多, 看的让人眼花缭乱, 最近有点时间, 把自己所了解的做一个总结, 直接进入主题了事件的传递主要有三个方法: dispatchTouchEvent(事件分发)onInterceptTouchEvent(事件拦截)onTouchEvent(事件消费)如下图:
事件 | Activity | ViewGroup | View |
---|---|---|---|
dispatchTouchEvent | 有 | 有 | 有 |
onInterceptTouchEvent | 没有 | 有 | 没有 |
onTouchEvent | 有 | 有 | 有 |
从上面的表格我们可以看出只有拦截事件比较特殊, 只存在 ViewGroup 中, 也就是我们只能在 ViewGroup 中才能重写该方法这三个方法都有返回值, 返回值为 true 的话表示该事件被消费, 事件传递终止, 反之返回 false, 事件继续传递 事件分成好几种类型, 我们常用的就三种, 从手指按下移动到抬起依次为: ACTION_DOWN(按下)ACTION_MOVE(移动)ACTION_UP(抬起)
事件的传递过程
事件的传递在我们手指按下 (ACTION_DOWN) 的瞬间发生了, 如果手指有移动会触发若干个移动事件(ACTION_MOVE), 当你手指抬起时会触发 ACTION_UP 事件, 这样为一个事件序列我们先来看看单个事件时怎么传递的, 如下一个 Demo, 一个 Activity 放有一个 ViewGroup,ViewGroup 放有一个 View, 其中 ViewGroup 和 View 都是我们自定义的, 分别继承与 ViewGroup 和 View 的子类, 代码如下: Activity 代码如下:
- public class TouchEventActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_touch_event);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 Activity 的 --->dispatchTouchEvent");
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 Activity 的 --->onTouchEvent");
- return super.onTouchEvent(event);
- }
- }
布局文件如下:
其中自定义控件 CustomLinearLayout 代码如下:
- public class CustomLinearLayout extends LinearLayout {
- ....
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->dispatchTouchEvent");
- return super.dispatchTouchEvent(ev);
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onInterceptTouchEvent");
- return super.onInterceptTouchEvent(ev);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onTouchEvent");
- return super.onTouchEvent(event);
- }
- }
CustomTextView 代码如下:
- public class CustomTextView extends TextView {
- ...
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 View 的 --->dispatchTouchEvent");
- return super.dispatchTouchEvent(event);
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 View 的 --->onTouchEvent");
- return super.onTouchEvent(event);
- }
- }
效果如下:
运行, 我们点击自定义的 TextView, 打印出来的日志如下:
什么意思呢? 不要着急, 接下来我慢慢解释首先我们知道一次点击, 会触发一次 ACTION_DOWN 若干个 ACTION_MOVE 一次 ACTION_UP 事件, 而一次事件的传递是由上往下传递的, 也就是依次通过 ActivityViewGroupView 按下的瞬间 ACTION_DOWN 触发, Activity 的 dispatchTouchEvent(事件分发)会先调用, 这个跟我们的第一行的日志不谋而合, 其实 Activity 的 dispatchTouchEvent 方法可以用下面的伪代码表示:
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
if (viewGroup 或者 view.dispatchTouchEvent(ev)) {
- return true;
- }
- return onTouchEvent(ev);
- }
这里我不贴源代码了, 伪代码看比较好理解 Activity 中的 dispatchTouchEvent 会调用 ViewGroup 或者 View 的 dispatchTouchEvent 的方法, 而当 ViewGroup 或者 View 返回 false 时才会调用本身的 onTouchEvent 方法第一行日就容易理解了, 是执行 Activity 的 dispatchTouchEvent 打印出来的, 而后会调用 CustomLinearLayout 的 dispatchTouchEvent 的事件分发方法, 这就有了第二行的日志
这里有个知识点, 就是 ViewGroup 的 dispatchTouchEvent 方法会调用自身的 onInterceptTouchEvent(事件拦截)方法, 这一点跟 Activity 中的有点不一样, 因为 Activity 中并没有事件拦截方法, 如果 ViewGroup 的 onInterceptTouchEvent 事件拦截方法返回 true, 那么 View 中的 dispatchTouchEvent 方法不会被调用, 反而会执行 ViewGroup 的 onTouchEvent 方法, 那么该事件 (ACTION_DOWN 事件) 传递结束
如果返回的是 false(默认就是返回 false), 那么 View 中的 dispatchTouchEvent 方法会被调用, 因为 View 是最底层的控件, 事件无法继续再往下传递, 只能自身消费, 所以 dispatchTouchEvent 又会调用 onTouchEvent 方法, 在我们这个例子中, onTouchEvent 返回的是默认值 false, 也就是没有消费该事件
我们知道事件的传递是从上往下传递的, 那么当事件传递到最底层的 View 并且该事件没有被消费, 又该如何呢? 其实上面的日志已经告诉我们了, 当最底层的 View 并没有消费该事件时, 该事件会一层层往上抛, 接下来会执行 ViewGroup 的 onTouchEvent 方法, 如果返回 true 的话, 事件传递停止, 如果还是一样返回默认值 false 的话, Activity 的 onTouchEvent 方法会被调用, 到此 ACTION_DOWN 事件的传递结束, 这就是一次完整的事件传递过程, 下图为事件传递的流程图:
细心同学又会问了, 既然事件的传递结束了, 为什么 Activity 的 dispatchTouchEventonTouchEvent 又被执行了两次呢(日志打印出来的)?
没错, 确实是执行了, 刚才我们说过了: 手指按下移动到抬起, 会执行一次 ACTION_DOWN(按下)若干次 ACTION_MOVE(移动)和一次 ACTION_UP(抬起), 被执行了两次是因为执行了一次 ACTION_MOVE(一次是偶然的, 如果你手指多滑动, 会执行多次的)和一次 ACTION_UP 事件, 也就是还有两次完整的事件传递过程, 但是我们发现后面这两次跟 ACTION_DOWN 不一样, 只调用两次 Activity 的 dispatchTouchEventonTouchEvent 方法, 这是为什么呢?
因为 Android 本身的事件传递机制就是这样的, 我们把手指按下抬起所发生的事件传递称为一个事件序列, 看似 3 个或 3 个以上独立的事件组成, 其实不然, 它们还是有联系的, 因为当 dispatchTouchEvent 在进行事件分发的时候, 只有前一个 action 返回 true, 才会触发下一个 action, 什么意思呢? 刚才的例子 Activity 的 dispatchTouchEvent 的方法中 viewGroup 或者 view.dispatchTouchEvent(ev)返回的是默认值 false, 接下来 ACTION_MOVEACTION_UP 两个事件并不会触发 ViewGroup 的 dispatchTouchEvent 方法(因为你前一个 actionACTION_UDOWN 返回的 false), 反而是直接执行自身的 onTouchEvent 的方法所以打印出来的日志就是这样的这告诉我们如果一个事件序列的 ACTION_DOWN 事件你没消费掉, 那么该事件序列的 ACTION_MOVEACTION_UP 并不会在被执行了
接下来我们稍微改一下代码, 把 CustomTextView 的 onTouchEvent 改成返回 true, 如下:
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 View 的 --->onTouchEvent");
- return true;
- }
运行点击, 我们来看一下日志, 如下图:
CustomTextView 的 onTouchEvent 消费了事件, 所以该序列的后续事件都会完整的传递到 CustomTextView 中, 并且都会在该方法中终止事件的传递
我们再来看看把 CustomTextView 的 dispatchTouchEvent 也改成直接返回 true, 是个什么情况, 完整的代码如下:
- public class CustomTextView extends TextView {
- ...
- @Override
- public boolean dispatchTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 View 的 --->dispatchTouchEvent");
- return true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 View 的 --->onTouchEvent");
- return true;
- }
- }
执行点击, 日志如下:
我们看到每次事件都会传递到 View 的 dispatchTouchEvent, 但是 onTouchEvent 并不会被执行, 关键代码就在 View 中的 dispatchTouchEvent 返回值不一样:
- return super.dispatchTouchEvent(ev);
- return true;
因为 onTouchEvent 是在 super.dispatchTouchEvent 方法中执行的, 所以我们虽然返回了 true, 每次事件都会传递过来但是并不会执行 onTouchEvent 方法
哈哈, 是不是有点复杂啊, 好好品味哈, 不然后面越看你会越乱
接下来我们也把 ViewGroup(CustomLinearLayout)代码也改一下, 把方法 onInterceptTouchEvent 的返回值改为 true, 代码如下:
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onInterceptTouchEvent");
- return true;
- }
点击执行, 日志如下:
我们可以看到事件传递到 ViewGroup 的 onInterceptTouchEvent 后会直接调用本身的 onTouchEvent 方法, 并没有把事件传递给 View 的 dispatchTouchEvent 方法, 因为返回 true, 就表明我们拦截了事件并把事件交给自己处理, 也阻止了事件继续往下传递, 但是我们虽然拦截了事件, 但并没有消费该事件, 所以后续的事件 ViewGroup 并没有接收到, 现在我们再把 ViewGroup 的 onTouchEvent 改为返回 true, 代码如下:
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onTouchEvent");
- return true;
- }
运行点击, 日志如下:
如我们所说, onTouchEvent 返回了 true 消费了该事件, 每次事件都会传递到 ViewGroup, 并且在 onTouchEvent 结束事件的传递, 但是你们发现没有 onInterceptTouchEvent 只会被执行一次, 没错就是这么奇葩, 只要你拦截了该事件, 就是这样的, onInterceptTouchEvent 并不会执行第二次, 读者记住, 不要问我为什么, 机制就是这样的, 为什么要这样, 我也不懂, 哈哈
接下来我们把 ViewGroup 的 dispatchTouchEvent 的返回值也改成 true, 完整的代码如下:
- public class CustomLinearLayout extends LinearLayout {
- ...
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->dispatchTouchEvent");
- return true;
- }
- @Override
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onInterceptTouchEvent");
- return true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 ViewGroup 的 --->onTouchEvent");
- return true;
- }
- }
一样, 点击, 运行, 日志如下:
是不是有点懵逼的感觉, 没错, 就是这么任性, 哈哈, 事件传递到 ViewGroup 的 dispatchTouchEvent 中, 这时, 我们返回 true, 表明我们消费了该事件, 所以后续事件都会继续传递过来, 但是我们直接把
return super.dispatchTouchEvent(ev);
改成:
return true;
ViewGroup 的 onInterceptTouchEvent 和 View 的 dispatchTouchEvent 并不会被执行了, 因为这些都放在 super.dispatchTouchEvent 里面执行的, 所以我们打印出来的日志就这样了, 有同学会问 Activity 的 onTouchEvent 为什么没被执行, 那你就没认真看之前的伪代码了, 我们返回了 true,Activity 的 onTouchEvent 是不会被执行的, 再好好想想哈
最后我们再改个地方的代码, 就是把 Activity 的 dispatchTouchEvent 的返回值改为返回 true, 完整的如下:
- public class TouchEventActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_touch_event);
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.e(getClass().getSimpleName(), "这是 Activity 的 --->dispatchTouchEvent");
- return true;
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- Log.e(getClass().getSimpleName(), "这是 Activity 的 --->onTouchEvent");
- return false;
- }
- }
运行, 点击, 日志如下:
我们看到只有 Activity 的 dispatchTouchEvent 被执行了 3 次, 知道为什么了吧, 这个就不仔细阐述了
这次的事件机制的讲解就先到这里, 下次我们再深入了解, 因为事件机制很复杂, 所以读者先好好理解这篇所讲的内容, 不然后面会越乱 (小编想说为了让你们看得简约一点, 点击一次刚好要执行一次 ACTION_MOVE 方法好累, 得试好多次, 哈哈, 看在这样, 请请请关注收藏, 谢谢了)
来源: https://juejin.im/entry/5a95585af265da4e7a78838d