Android事件分发机制详解


今天又是周五啦,提前祝大家周末愉快!


本篇来自 西电吴同学 的投稿,是他自己的学习事件分发的笔记。一千个人眼里有一千个哈姆雷特,对于事件分发机制,分析的文章也很多,表述的手法,切入顺序也不一样。本篇着重的是View的事件分发。(这里打个广告,最强的分析可以看我的博客)。


西电吴同学 的博客地址:

http://blog.csdn.net/a62321780


概述


之前在学习Android事件方法机制的时候,看过不少文章,但是大部分都讲的不是很清楚,我自己理解的也是云里雾里,也尝试过阅读源码,看得我更是不知所措。最近阅读了《Android开发艺术探索》一书中相关的章节,茅塞顿开,写下本文作为阅读笔记,以便以后查阅。


三个重要方法


public boolean dispatchTouchEvent(MotionEvent ev)


事件传递过来的时候这个方法第一个被调用,返回结果受 当前View的ontouchEvent()方法 或者 下一级View的dispatchTouchEvent()方法 返回值影响。


public boolean onInterceptTouchEvent(MotionEvent ev)


这个方法是在 dispatchTouchEvent()方法 内部掉用的,返回值用来判断是否拦截当前事件。


public boolean onTouchEvent(MotionEvent ev)


也是在 dispatchTouchEvent()方法 中掉用,用来处理某一事件。


事件传递规则


书中用了一段伪代码来表示:




也就是说当一个事件到来的时候,当前View的dispatchTouchEvent方法 会被调用,在内部首先调用 onInterceptTouchEvent 判断是否拦截,如果拦截,将事件传递给自己的 onTouchEvent 对事件进行处理。如果不拦截,就将事件传递给子View,调用 子View的dispatchTouchEvent方法,一直到事件被消费。


源码分析


上面的内容讲的很抽象,不好理解,接下来配合源码来讲解,这样更加的容易深入理解事件分发机制。


判断是否拦截


事件到来的时候,View的第一个工作自然是判断是否拦截,下面给出 dispatchTouchEvent 中拦截的相关代码:




这里要注意的是,事件分发机制针对的其实可以看作是一系列的事件,也就是一个事件序列,也就是说一个事件序列由 一个DOWN开头,中间n个MOVE,然后以UP或者CANCEL结束


代码中 mFirstTouchTarget 在子元素成功处理事件的时候会进行赋值,也就是说 当事件不是DOWN,而且没有子元素成功处理的时候,直接拦截事件自己处理。这很好理解,如果不是DOWN说明事件序列已经开始传递了,那么如果子元素不处理 最开始的DOWN 说明它不想要这个序列,那么就自己处理,一直到新的事件序列到来(也就是 新的DOWN)。也就是说一旦我们处理一个事件就不会多次调用onInterceptTouchEvent方法


另一种情况是 DOWN到来,也就是新的事件序列开始,或者 子View 成功处理过这个序列,就会进行判断。判断第一步是判断 FLAG_DISALLOW_INTERCEPT 标志位,这个标志位是通过 requestDisallowInterceptTouchEvent 方法设置的,一般是 子View 调用的,如果不允许拦截,就不拦截。如果允许,那就调用自己的 onInterceptTouchEvent 方法来判断。


值得注意的是当 DOWN事件到来 的时候,会重置标志位,且 清除mFirstTouchTarget,就是新序列到来的时候一切重置。




不拦截事件


如果最后不拦截事件,那么就应该分发下去:




就是 遍历子View,通过是否在播放动画和事件是否落在它的范围内来获得合适的 View,如果存在就调用它的 dispatchTouchEvent 方法。 


我们需要获得 dispatchTouchEvent 返回的值来判断 子View 是否成功消耗了事件,如果返回的是 true 代表成功消费,那么就会对 mFirstTouchTarget 进行赋值。


newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;


这个赋值很重要,如果不消耗那么就不会赋值,也就是说 mFirstTouchTarget== null,那么接下来的事件(同一序列,也就不会再产生DOWN了)都由本 View 消耗,不再分发。


当然,如果最后发现 没有合适的子View 或者 子View返回了false,那么都由 本View 处理,也就是 onTouchEvent,这也就是 为什么事件到了最底层还没被消耗(返回true)就会重新向上传递到上一层的onTouchEvent处理的原因了


拦截事件


那就开始自己处理事件,接下来的内容就不详细讲解了。


View对事件的处理:




这里的 View不包含ViewGroup,可以看到当要处理事件的时候首先判断是否设置了 OnTouchListener,如果设置了就调用 onTouch方法。如果 onTouch返回了 true,那么就直接返回,不会去调用ontouchEvent。如果返回了false,就回调用 ontouchEvent,返回 onTouchEvent的返回值。 


onTouchEvent 内部,如果设置了 OnClickListener 就会调用 onClick方法


总的来说,就是 onTouchListener 级别高于 onTouchEvent,onClickListener 最低


案例分析


针对上述的理论分析,我们通过以下的Demo来结合实践加深理解。


首先自定义一个 MyViewGroup MyView,代码如下:


MyView:




MyViewGroup:




这里我们 拦截了DOWN事件,接下来点击 MyView 的区域然后 滑动,最后 抬起



打印结果如下:




明明滑动了一段距离,理论上有很多个MOVE事件,为什么只有三个打印呢?其实之前就已经说明了,我们 拦截了DOWN事件,那么子元素是收不到 DOWN事件 的,结果就是该序列接下来的事件都是我们自己消费,且不会再次掉用 onInterceptTouchEvent,由自己的 onTouchEvent 处理。因为我们的 onTouchEvent 返回了 false,直接导致我们的 dispatchTouchEvent 也返回了 false。那么 MyViewGroup 的上一层就不会把接下来的事件传递给我们了(上一层的 mFirstTouchTarget 没有赋值),所以接下来的事件都不会到来。


我们再改变一下,让 MyViewGroup的onTouchEvent方法返回 true,进行相同的操作,打印结果如下:




省略的打印信息就是 第二条和第三条的多次重复,也就是说在接下来的 MOVE到来的时候,由于之前拦截了 DOWN,所以事件自己处理,不会再掉用 onIntereptTouchEvent


注意事项


  • 一般在处理滑动冲突的时候重写相关方法,对于DOWN事件是不会拦截的,也就是返回 false,在接下来的 MOVE序列 中判断是否需要拦截。因为如果拦截了DOWN,那么接下来的事件都不会传给子View了,之前已经分析过了。


  • 一般也不会拦截 UP事件,因为UP一般为序列的最后一个事件,拦截不拦截对自己没有什么用处,但是子View就可能因为收不到UP而无法触发click事件






如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。


欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值