Android回顾---View事件体系

说到事件分发首先得说一下MotionEvent,MotionEvent代表了手指对屏幕的一系列操作,主要包括四个事件:

  • ACTION_DOWN:手指触摸屏幕的瞬间触发
  • ACTION_MOVE:手指在屏幕上移动时触发
  • ACTION_UP:手指离开屏幕时触发
  • ACTION_CANCEL:这个是取消事件,非人为的。(如果我们把事件交给最底层的子View来消费,就会给父View(ViewGroup)设置一个不可拦截的flag,那么后续的事件父View默认都不会处理,但是不代表父View不能拦截了,如果此时父View对此事件进行了拦截,那么父View就会给子View分发一个ACTION_CANCEL事件)

所以我们主要是注意前三个事件。


然后就是事件分发的三个方法:

  • dispatchTouchEvent:点击事件传递的方法。当方法返回值为true时表示事件被当前视图消费掉;如果返回false时则表示交给父类中的onTouchEvent进行处理;如果返回为super.dispatchTouchEvent时则表示会继续分发该事件。

  • onInterceptTouchEvent:判断是否拦截事件

    当方法返回值为true时表示拦截这个事件并交由自身的onTouchEvent方法进行消费;如果返回false则表示不拦截,需要继续传递给子视图。如果返回super.onTouchEvent(ev),这块就有点麻烦了,分为两种情况:

    • 如果该View存在子View且点击到了该子View, 则不拦截, 继续分发给子View 处理, 此时相当于返回 false
    • 如果该View没有子View或者有子View但是没有点击中子View(此时ViewGroup 相当于普通View), 则交由该View的onTouchEvent响应,此时相当于返回true。
  • onTouchEvent:处理点击事件

    这三个方法间的关系用伪代码可以表示成在dispatchTouchEvent方法中有一个if,if的判断条件就是onInterceptTouchEvent,如果这个方法返回true,就调用onTouchEvent,返回false的话就让dispatchTouchEvent返回false

最后一点就是,Android的屏幕是由Activity、ViewGroup和View组成的,所以事件分发应该先从Activity开始。

一个Activity包含了一个Window对象,Window是由PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,而这个DecorView又将屏幕划分为两个区域:一个是 TitleView,另一个是ContentView,而我们平时所写的就是展示在ContentView中的。

Activity

事件分发的方法就是Activity的dispatchTouchEvent方法,这个方法中会进入getWindow的superDispatchTouchEvent,而getWindow就是WIndow类,而window类的唯一实现类就是PhoneWindow,所以实际上是调用了PhoneWindow的dispatchTOuchEvent方法,而PhoneWindow实际上又是调用的DecorView的superDispatchTouchEvent方法,而DecorView是FrameLayout的子类,FrameLayout是ViewGroup的子类,这时事件就从Activity层传到了ViewGroup层。但是如果不拦截的话也就是activity的getWindow.dispatchTouchEvent方法返会false的话,就会调用activity的onTouchEvent方法,在这个方法中主要是判断点击事件是否在边界外,如果在边界内,就消费事件返回true如果不在边界内,就放弃时间的处理。返回false。

ViewGroup

在ViewGroup中首先需要对事件进行一个判断,如果是ACTION_DOWN或者前几个事件有子View拦截的话,就判断是否设置了不拦截的flag,如果没有设置,就调用onInterceptTouchEvent判断是否需要拦截,默认不拦截。如果设置了flag,就直接判定为不拦截。如果该ViewGroup一开始就拦截了ACTION_DOWN,这里就默认拦截。也就是说如果他拦截过事件,这个序列中的其它事件就默认都拦截了,不回去调用onInterceptTouchEvent去判断。最后再根据是否拦截来判断是ViewGroup自己消耗事件还是继续向子View分发,如果不拦截事件就遍历子View,然后依次调用他们的dispatchTouchEvent。如果拦截就调用ViewGroup的dispatchTransformTouchEvent进行处理,其内部是调用其父类的dispatchTouchEvent。

为什么遍历子View是倒序的

是考虑到我们平时的生活习惯,给一块区域设置点击事件的时候,一般都是给最上层的View去设置,而不是给覆盖在它下面的View去设置,倒序遍历,从最上层的View往内去寻找是否有View接收事件,符合日常体验,大概率上来看,如果有View接受事件,倒序是一种更快找出View的遍历方式。

View

当一个事件传到View的时候,首先会判断是否设置了onTouchListener,如果有就调用onTouch去消耗,没有在调用onTouchEvent去消耗,onTouchEvent中会判断是否设置了click、longclick以及contextlick,如果有,就去消耗。也就是说先执行onTouch在执行onclick。如果onTouch先执行了,那么久不会去管onCLick。


滑动冲突

View的滑动冲突分为三种,一种是内外布局同向冲突,一种是不同向的冲突,还有一种是前两种的嵌套。

对于场景1来说,比如说外部需要左右滑动时,当发生左右滑动时,让外部拦截。当发生上下时,让内部滑动。

对于场景2来说,他无法根据滑动的角度,距离差和速度差来判断,但他一般都能在业务上找到突破点。比如,业务规定,当处于某种状态时是,外部View响应,当处于另一种状态时,内部View响应。根据这个规则对滑动进行相应的处理。

场景3是两者的嵌套,只要一层层解决就好。

对于滑动冲突一般有两种解决方法,一种是外部拦截法,一种是内部拦截法。

具体见此文章里的ScrollView嵌套ViewPager样例:

ScrollView里嵌套ListView并没有滑动冲突,但是会有显示冲突,只能显示一行ListView。但是这一行的范围内是可以正常滑动的。

问题

ACTION_CANCEL什么时候触发,触摸button然后滑动到外部抬起会触发点击事件吗,再滑动回去抬起会么?

  • 一般ACTION_CANCEL和ACTION_UP都作为View一段事件处理的结束。如果在父View中拦截ACTION_UP或ACTION_MOVE,在第一次父视图拦截消息的瞬间,父视图指定子视图不接受后续消息了,同时子视图会收到ACTION_CANCEL事件。
  • 如果触摸某个控件,但是又不是在这个控件的区域上抬起(移动到别的地方了),就会出现action_cancel。

点击事件被拦截,但是想传到下面的View,如何操作?

重写子类的requestDisallowInterceptTouchEvent()方法返回true就不会执行父类的onInterceptTouchEvent(),即可将点击事件传到下面的View。

点击事件是如何被触发的

我们都知道如果给一个控件注册了touch事件,每次点击它的时候都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等一组事件。按理说每次事件都会触发dispatchTouchEvent。可是如果一组事件中的某个事件的dispatchTouchEvent返回了false,那表示这组事件已经处理完毕,后面的事件不会触发dispatchTouchEvent。

例如ACTION_DOWN的dispatchTouchEvent返回了false,后面一系列其它的action就不会再得到执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个action的dispatchTouchEvent返回true(这里是指 dispatchTouchEvent返回true而不是onTouch 返回true) ,才会触发后一个action。(这里的action和事件是一个意思)

如果一个view是clickable或者longclickable,那么他永远会消费action_up事件,在onTouchEvent里面消费掉,不会传递给他的parent

Action_Cancel机制

首先来看个例子:

在ViewGroup中有一个Button:

  1. 在VIewGroup拦截事件,那么Button就会出现一个Action_Cancel。如果ViewGroup拦截了Move事件,那么这个Move事件将会转化为Cancel事件传递给子view
  2. 如果ViewGroup不拦截,那就不会出现Cancel,只有move和up

某一个子View处理了Down事件,那么随之而来的Move和Up事件也会交给它处理。但是交给它处理之前,父View还是可以拦截事件的,如果拦截了事件,那么子View就会收到一个Cancel事件,并且不会收到后续的Move和Up事件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值