#一文读懂系列# View事件分发机制

Android View 虽然不是四大组件,但其并不比四大组件的地位低。而View的核心知识点事件分发机制则是不少刚入门同学的拦路虎。ScrollView嵌套RecyclerView(或者ListView)的滑动冲突这种老大难的问题的理论基础就是事件分发机制。

MotionEvent事件初探

我们对屏幕的点击,滑动,抬起等一系的动作都是由一个一个MotionEvent对象组成的。根据不同动作,主要有以下三种事件类型:

  1. ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件
  2. ACTION_MOVE:手指在屏幕上移动时候产生该事件
  3. ACTION_UP:手指从屏幕上松开的瞬间产生该事件

从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列

MotionEvent事件分发

当一个MotionEvent产生了以后,就是你的手指在屏幕上做一系列动作的时候,系统需要把这一系列的MotionEvent分发给一个具体的View。我们重点需要了解这个分发的过程,那么系统是如何去判断这个事件要给哪个View,也就是说是如何进行分发的呢?

事件分发需要View的三个重要方法来共同完成:

  • public boolean dispatchTouchEvent(MotionEvent event)
    通过方法名我们不难猜测,它就是事件分发的重要方法。那么很明显,如果一个MotionEvent传递给了View,那么dispatchTouchEvent方法一定会被调用!
    返回值:表示是否消费了当前事件。可能是View本身的onTouchEvent方法消费,也可能是子View的dispatchTouchEvent方法中消费。返回true表示事件被消费,本次的事件终止。返回false表示View以及子View均没有消费事件,将调用父View的onTouchEvent方法
  • public boolean onInterceptTouchEvent(MotionEvent ev)
    事件拦截,当一个ViewGroup在接到MotionEvent事件序列时候,首先会调用此方法判断是否需要拦截。特别注意,这是ViewGroup特有的方法,View并没有拦截方法
    返回值:是否拦截事件传递,返回true表示拦截了事件,那么事件将不再向下分发而是调用View本身的onTouchEvent方法。返回false表示不做拦截,事件将向下分发到子View的dispatchTouchEvent方法。
  • public boolean onTouchEvent(MotionEvent ev)
    真正对MotionEvent进行处理或者说消费的方法。在dispatchTouchEvent进行调用。
    返回值:返回true表示事件被消费,本次的事件终止。返回false表示事件没有被消费,将调用父View的onTouchEvent方法

上面的三个方法可以用以下的伪代码来表示其之间的关系。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;//事件是否被消费
        if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件
            consume = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
        }else{
            consume = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
        }
        return consume;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
    }

接下来我们来看一下ViewViewGroup 在事件分发的时候有什么不一样的地方:
ViewGroup是View的子类,也就是说ViewGroup本身就是一个View,但是它可以包含子View(当然子View也可能是一个ViewGroup),所以不难理解,上面所展示的伪代码表示的是ViewGroup 处理事件分发的流程。而View本身是不存在分发,所以也没有拦截方法(onInterceptTouchEvent),它只能在onTouchEvent方法中进行处理消费或者不消费。
总体就是View由于不能包含子View所以没有分发事件的意思,故它没有onInterceptTouchEvent方法。

但是这里有三点需要特别强调一下

  • 子View可以通过requestDisallowInterceptTouchEvent方法干预父View的事件分发过程(ACTION_DOWN事件除外),而这就是我们处理滑动冲突常用的关键方法。关于处理滑动冲突,我们下一篇文章会专门去分析,这里就不做过多解释。
  • 对于View(注意!ViewGroup也是View)而言,如果设置了onTouchListener,那么OnTouchListener方法中的onTouch方法会被回调。onTouch方法返回true,则onTouchEvent方法不会被调用(onClick事件是在onTouchEvent中调用)所以三者优先级是onTouch->onTouchEvent->onClick
  • View 的onTouchEvent 方法默认都会消费掉事件(返回true),除非它是不可点击的(clickable和longClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false。

这里有一个很好的例子,如果ScrollView内部嵌套ListView,那么ListView是不可以滑动的。
在这里插入图片描述
原本应该被ListView用来上下滑动的事件,被ScrollView拦截了。就导致ListView不能正常滑动。这里就出现了典型的滑动冲突现象。

根据上面所提供的思路,我在子View中也就是ListView,在他的onTouchEvent方法中请求父View不要拦截事件即可,也就是调用requestDisallowInterceptTouchEvent(false)即可

View事件分发

  • 当ViewGroup决定拦截事件后,后续事件将默认交给它处理并且不会再调用onInterceptTouchEvent方法来判断是否拦截。子View可以通过设置FLAG_DISALLOW_INTERCEPT标志位来不让ViewGroup拦截除ACTION_DOWN以外的事件。
  • 所以我们知道了onInterceptTouchEvent并非每次都会被调用。如果要处理所有的点击事件那么需要选择dispatchTouchEvent方法
    而FLAG_DISALLOW_INTERCEPT标志位可以帮助我们去有效的处理滑动冲突

如何onInterceptTouchEvent方法不拦截的话,就会下发给他的子View去处理

  • ViewGroup会遍历所有子View去寻找能够处理点击事件的子View(可见,没有播放动画,点击事件坐标落在子View内部)最终调用子View的dispatchTouchEvent方法处理事件
  • 当子View处理了事件则mFirstTouchTarget 被赋值,并终止子View的遍历。
  • 如果ViewGroup并没有子View或者子View处理了事件,但是子View的dispatchTouchEvent返回了false(一般是子View的onTouchEvent方法返回false)那么ViewGroup会去处理这个事件(本质调用View的dispatchTouchEvent去处理)

子View处理事件

View会先判断是否设置了OnTouchListener,如果设置了OnTouchListener并且onTouch方法返回了true,那么onTouchEvent不会被调用。
当没有设置OnTouchListener或者设置了OnTouchListener但是onTouch方法返回false则会调用View自己的onTouchEvent方法。

  1. View是disabled状态,依然不会影响事件的消费,只是它看起来不可用。
  2. 只要CLICKABLE和LONG_CLICKABLE有一个为true,就一定会消费这个事件,就是onTouchEvent返回true。这点也印证了我们前面说的View 的onTouchEvent 方法默认都会消费掉事件(返回true)
  3. 除非它是不可点击的(clickable和longClickable同时为false),View的longClickable默认为false,clickable需要区分情况,如Button的clickable默认为true,而TextView的clickable默认为false。

文献

1. 一文读懂Android View事件分发机制
2. 带着问题来思考整个事件分发过程

购物商城项目采用PHP+mysql有以及html+css jq以及layer.js datatables bootstorap等插件等开发,采用了MVC模式,建立一个完善的电商系统,通过不同用户的不同需求,进行相应的调配和处理,提高对购买用户进行配置….zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值