前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >笔记11 - Android touch事件分发时机

笔记11 - Android touch事件分发时机

作者头像
码农帮派
发布2021-01-14 16:28:57
8050
发布2021-01-14 16:28:57
举报
文章被收录于专栏:码农帮派码农帮派

需要首先清楚的两个概念:

ViewGroup

ViewGroup是一组View的组合,在其内部会包含多个子View,当用户点击屏幕的时候,点击的位置可能在ViewGroup上,也可能在其内部View控件上。

因此ViewGroup事件分发的重点在于如何处理当前ViewGroup和子View之间事件分发的逻辑关系:

  • 1. 当前ViewGroup是否需要拦截touch事件;
  • 2. 是否需要将touch事件继续分发给子View;
  • 3. 如何将touch事件分发给子View。

ViewGroup中有touch相关的回调有三个:

代码语言:javascript
复制
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onInterceptTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);

View

View是最小的控件单位,不能够再被细分,所以View的事件分发重点在于如何处理处理touch事件:

  • 1. 是否设置了TouchListener;
  • 2. 是否自己接收处理touch事件。

View中有两个touch相关的回调事件:

代码语言:javascript
复制
public boolean dispatchTouchEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);

事件分发的核心dispatchTouchEvent

整个事件的分发就是一个大的递归函数,而这个递归函数就是dispatchTouchEvent,在这个递归函数中适时的调用onInterceptTouchEvent函数来拦截事件,或者调用onTouchEvent来处理事件。

dispatchTouchEvent中会经历三个步骤:

  • 步骤1. 检查当前ViewGroup是否需要拦截此事件,如果需要拦截(onInterceptTouchEvent => true),那么该touch事件不会传递给子View(或者以Cancel的方式通知子View);
  • 步骤2. 如果当前ViewGroup没有拦截此事件(onInterceptTouchEvent => false),则将事件继续分发给子View,子View将此事件捕获,则将mFirstTouchTarget赋值给该子View;
  • 步骤3. 根据mFirstTouchTarget重新分发事件。

步骤1的具体实现:

如果事件为DOWN事件,则调用onInterceptTouchEvent进行事件拦截判断;

或者mFirstTouchTarget不为null,代表已经有子View捕获了这个事件,子View的dispatchTouchEvent返回true,代表当前的View捕获touch事件。

步骤2的具体实现:

图中1表明事件主动分发的前提是DOWN事件,事实上在分发DOWN事件的过程中,找到具体处理该DOWN事件的View之后,后续的MOVE和UP事件就都会分发给该View,不再需要dispatch的判断。

图中2在遍历ViewGroup下所有的子View,需要注意的是,遍历的方式是从后向前遍历,要是发现某一个子View接收touch事件,则停止遍历,这是因为Android中子View是从上向下放到子View数组中,后面的View在前面View的上面,要是上面的View可接收touch,那么就会遮挡下层View的事件接收(xml布局中,同一个ViewGroup中包裹的View,写在后面的View覆盖在上面,写在前面的View被后面的View覆盖的)

图中3在判断touch事件的坐标是否在子View的坐标范围内,并且子View没有处于动画状态。

图中4中通过调用dispatchTransformedTouchEvent方法将事件分发给子View,要是子View接收处理该事件,则该函数返回true,并将mFirstTouchEvent赋给该子View,之后的MOVE和UP事件也都会直接分发给该子View。

步骤3的具体实现:

步骤3中有两个分支判断:

  • 要是mFirstTouchTarget为null,说明在上述的事件分发中并没有子View对事件进行捕获处理,这种情况下,最终会调用super.dispatchTouchEvent方法,将该touch事件传回给ViewGroup,最终传到DecorView,并传回到Activity进行处理。
  • 如果mFirstTouchTarget不为null,说明步骤2中已经有子View对touch事件进行了捕获,则直接将当前已经后续的事件(MOVE和UP事件)交给该View进行处理。

为什么DOWN事件特殊

所有额touch事件都是从DOWN事件开始的,这是DOWN事件比较特殊的原因之一,另外一个原因是DOWN事件的捕获结果会直接影响后续的MOVE和UP事件的处理。

在事件分发的过程中,只有DOWN事件会传递给子View进行事件捕获的判断,一旦某个子View捕获了DOWN事件,就会将mFirstTouchTarget赋值给这个View,后续的MOVE和UP事件,也会通过遍历mFirstTouchTarget链表查找到该View,并将MOVE和UP事件直接分配给该View。也就是后续的MOVE、UP事件下发给谁,都是由起始的DOWN事件被谁捕获决定的。

mFirstTouchTarget的作用

mFirstTouchTarget的类型是TouchuTarget。

上面TouchTarget的源码可以看出,TouchTarget是一个链表结构,这个mFirstTouchTarget的作用就是为了记录捕获了touch事件的View,会将这个View保存在TouchTarget的child上,如果mFirstTouchTarget不为null,则后续的事件全部分发给TouchTarget的child进行处理。

在向子View分发事件的代码中,有一段源码:

上面的代码中,红色框处target不为null,表明已经有子View捕获了该touch事件,但是蓝色框的地方,intercepted又变成了true,表示父ViewGroup开始拦截该事件,父ViewGroup拦截事件,就会使得该事件不会传递到子View中,这种情况下,父ViewGroup会给子View传入cancelChild = true。

触发这段逻辑的场景

当父ViewGroup的onInterceptTouchEvent返回false,然后在子View的dispatchTouchEvent中返回了true(表示子View捕获事件),在之后MOVE的过程中,父ViewGroup的onInterceptTouchEvent又返回了true,intercepted重新置为true,此时上面的逻辑就会被触发,子View就会收到一个ACTION_CANCEL的事件。

总结:

  • 1. 对于dispatchTouchEvent、onTouchEvent,return true是终结事件的传递。return false是回传给父ViewGroup的onTouchEvent方法;
  • 2. ViewGroup想要把事件分发给自己的onTouchEvent,需要让拦截器onInterceptTouchEvent返回true把事件拦截下来;
  • 3. ViewGroup的拦截器onInterceptTouchEvent默认是不拦截事件的,return super.onInterceptTouchEvent相当于return false;
  • 4. View没有拦截器,为了让View可以将事件分发给自己的onTouchEvent中,View的dispatchTouchEvent的super默认实现会将事件分发给自己的onTouchEvent;

关于dispatchTouchEvent、onInterceptTouchEvent以及onTouchEvent的说明:

ViewGroup和View的dispatchTouchEvent是做事件的分发的,事件可能分发的目标:

  • 1. 自己消费、中介传递。返回true
  • 2. 给自己的onTouchEvent处理,dispatchTouchEvent调用super方法,super会调用拦截器onInterceptTouchEvent,在拦截器中返回true,就会将事件分发给自己的onTouchEvent。
  • 3. 传递给子View,dispatchTouchEvent会调用super方法,super方法默认会调用拦截器onInterceptTouchEvent,在拦截器中返回false。

ViewGroup和View的onTouchEvent是做事件处理,有两种处理方式:

  • 1. 自己消费掉,事件终止,onTouchEvent返回true;
  • 2. 继续从下往上传递,事件回溯,不消费事件,让父ViewGroup的onTouchEvent可以接收到这个事件,onTouchEvent返回false。super等价于false

ViewGroup和View的onInterceptTouchEvent对于事件的处理有两种:

  • 1. 拦截下来,给自己的onTouchEvent处理;
  • 2. 不拦截,把事件传递给子View。super等价于false。

如上图,事件的分发流程是一个U形图,从activity分发到最终的子View的过程会调用整个事件分发链路上的dispatchTouchEvent,当然ViewGroup在接收事件的时候,还会调用自己的拦截器,以便让事件调用到达自己的onTouchEvent中;

要是最终的子View没有接收事件,那么事件会回溯到activity,回溯的流程中会调用到链路上的onTouchEvent。

举例1:

ViewGroup2的dispatchTouchEvent返回true,事件的分发会终止在此,如上图中的红色箭头,蓝色箭头是事件后续的MOVE和UP的流向。此时ViewGroup2还要通过onInterceptTouchEvent拦截事件,从而触发onTouchEvent。

举例2:

子View的onTouchEvent返回true,此时事件的传递如上图。

举例3:

ViewGroup1的onTouchEvent返回true,这种情况下ViewGroup的dispathTouchEvent返回false,表示不拦截事件,事件一直向下分发,直到子View向上回溯事件,在ViewGroup1的onTouchEvent返回true消费事件,可以看到红色事件的分发流程和后续事件MOVE和UP的分发并不一致。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农帮派 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档