Android View事件分发机制 onTouch和onTouchEvent区别 以及解决滑动冲突的思路

参考: https://www.jianshu.com/p/7d50a6b0b6af

拦截顺序:

activity的dispatch-->父容器dispatch--->父容器intercept--->子容器的ontouch(一般return 值为true)--->父容器的ontouch-->activity的onTouch

如果中间被拦截,就交由该层级的onTouch事件处理

view中的监听事件优先级:OnTouchListener---->OnTouchEvent---->OnCLickListener

onTouchOListener不重写,默认返回false(重写为true则不会将事件交给touchevent),事件依旧交给onTouchEvent处理,如果onTouchEvent中的down和up事件都返回的是super.onTouchEvent,则能够触发onClickListener中的click事件

如果某个子view只在onTouchEvent中的down事件返回false,其他都是true,那么这个事件将会给父容器onTouch来处理,且相当于这个事件的down move up都会给父容器,子容器由于没有接收到down事件  后续一系列事件都不会给子容器实现;

相反的,如果这个子view在onTOuchEvent中的down事件返回true,而其他都是false,那么这个事件down move up依旧还是给子容器处理,case move up代码中都能够走到

注意:requestDisallowInterceptTouchEvent只针对于父容器intercept方法中down必须为false才能影响父容器的拦截,如果为true,那么整个系列的事件都由父容器完成了 不会再交给子容器来做;如果父容器想保留down move up事件,在intercept方法中的move 和 up返回true即可,但是这样 子view就拿不到move和up事件 将无法完成监听点击等事件;因此down move和up需要在父容器intercept dispatch中和子view的onTouch或者dispatch中进行取舍,  但是最重要的是父容器的down一定不能为true,要不然子view无论怎么拦截都拿不到后续事件了。

总结一下:

子View中的监听事件优先级:  OnTouchListener---->OnTouchEvent---->OnCLickListener

如果优先级高的拦下事件,就不会再传递给后面的监听事件

1.Touch事件分发中只有两个主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三个相关事件。View包含dispatchTouchEvent、onTouchEvent两个相关事件。其中ViewGroup又继承于View。

2.ViewGroup和View组成了一个树状结构,根节点为Activity内部包含的一个ViwGroup。

3.触摸事件由Action_Down、Action_Move、Aciton_UP组成,其中一次完整的触摸事件中,Down和Up都只有一个,Move有若干个,可以为0个。

4.当Acitivty接收到Touch事件时,将遍历子View进行Down事件的分发。ViewGroup的遍历可以看成是递归的。分发的目的是为了找到真正要处理本次完整触摸事件的View,这个View会在onTouchuEvent结果返回true。

5.当某个子View返回true时,会中止Down事件的分发,同时在ViewGroup中记录该子View。接下去的Move和Up事件将由该子View直接进行处理。由于子View是保存在ViewGroup中的,多层ViewGroup的节点结构时,上级ViewGroup保存的会是真实处理事件的View所在的ViewGroup对象:如ViewGroup0-ViewGroup1-TextView的结构中,TextView返回了true,它将被保存在ViewGroup1中,而ViewGroup1也会返回true,被保存在ViewGroup0中。当Move和UP事件来时,会先从ViewGroup0传递至ViewGroup1,再由ViewGroup1传递至TextView。

6.当ViewGroup中所有子View都不捕获Down事件时,将触发ViewGroup自身的onTouch事件。触发的方式是调用super.dispatchTouchEvent函数,即父类View的dispatchTouchEvent方法。在所有子View都不处理的情况下,触发Acitivity的onTouchEvent方法。

7.onInterceptTouchEvent有两个作用:1.拦截Down事件的分发。2.中止Up和Move事件向目标View传递,使得目标View所在的ViewGroup捕获Up和Move事件。

ViewGroup的相关事件有三个:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相关事件只有两个:dispatchTouchEvent、onTouchEvent。

先分析ViewGroup的处理流程:首先得有个结构模型概念:ViewGroup和View组成了一棵树形结构,最顶层为Activity的ViewGroup,下面有若干的ViewGroup节点,每个节点之下又有若干的ViewGroup节点或者View节点,依次类推。如图: 
这里写图片描述

当一个Touch事件(触摸事件为例)到达根节点,即Acitivty的ViewGroup时,它会依次下发,下发的过程是调用子View(ViewGroup)的dispatchTouchEvent方法实现的。简单来说,就是ViewGroup遍历它包含着的子View,调用每个View的dispatchTouchEvent方法,而当子View为ViewGroup时,又会通过调用ViwGroup的dispatchTouchEvent方法继续调用其内部的View的dispatchTouchEvent方法。上述例子中的消息下发顺序是这样的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只负责事件的分发,它拥有boolean类型的返回值,当返回为true时,顺序下发会中断。在上述例子中如果⑤的dispatchTouchEvent返回结果为true,那么⑥-⑦-③-④将都接收不到本次Touch事件。

ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。

滑动冲突解决:

思路一:

父容器中事件拦截onInterceptTouchEvent的逻辑如以下代码所示:

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        switch(ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                if(满足我们需要的条件){
                    intercept = true;
                }else{
                    intercept = false;
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
            default:
                break;
        }
        return intercept;
    }

其中ACTION_DOWN事件不能进行拦截,因为在之前分发机制的分析时我们发现,如果拦截了ACTION_DOWN事件,那么之后的所有事件都会交给拦截的view进行处理。这里UP事件返回false是因为,如果不让子View拿到Up事件,那么子view的点击事件也将无法完成,这将对按钮等空间(不需要点击的控件可返回true)会产生巨大影响。

思路二:

(2)利用子View的requestDisallowInterceptTouchEvent(true)方法

这个思路就是父布局默认拦截除了ACTION_DOWN的所有事件,子View中在dispatchTouchEvent方法中根据需要来干预父布局的拦截策略。默认不允许父布局拦截事件,在需要父布局处理事件时,通过requestDisallowInterceptTouchEvent(false)方法让父布局处理事件,其他时候都由子View处理。

注意点:

  • 同样的对于ACTION_DOWN事件,onInterceptTouchEvent方法必须返回false,其他事件默认返回true
  • 在子View的dispatchTouchEvent方法中,对于ACTION_DOWN事件,通过调用requestDisallowInterceptTouchEvent(true)默认不允许父布局拦截事件,这样后续事件都交给子View处理
  • 在子View的dispatchTouchEvent方法中,对于ACTION_MOVE事件,默认是子View处理,在需要父布局处理时,调用requestDisallowInterceptTouchEvent(false)方法来让父布局拦截事件,交给父布局处理。

子view:

override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
    ev?.run {
        when(action){
            MotionEvent.ACTION_DOWN -> parent.requestDisallowInterceptTouchEvent(true)
            MotionEvent.ACTION_MOVE ->{
                if(满足需要让外部容器拦截事件){
                    parent.requestDisallowInterceptTouchEvent(false)
                }
            }
        }
    }
    return super.dispatchTouchEvent(ev)
}

这里需要提一点,retuan true 是告诉系统当前的View需要处理这次的touch事件,以后的系统发出的ACTION_MOVE,ACTION_UP还是需要继续监听并接收的,而且这次的action已经被这个View给消费掉了,父层的ViewGroup将不会走onTouchEvent了。所以每一个action最多只能有一个onTouchEvent接口返回true。如果return false,这个action会传向父级,调用父级View的onTouchEvent。但是这一次的touch事件之后发出的任何action,该View都不会再接受,onTouchEvent在这一次的touch事件中再也不会触发,也就是说一旦View返回false,那么之后的ACTION_MOVE,ACTION_UP等ACTION就不会在传入这个View,但是下一次touch事件的action还是会传进来的。所以在这边把返回值统一置为true,避免子View主动的将事件回传给父层ViewGroup。

onTouch和onTouchEvent、onLongClickListener和OnClickListener:

关于这几个概念的混淆:

这几个的前提是子View必须持有事件,父容器的所有拦截事件都没把事件给拦下来,最后传到子View,子View再传回去的路上才会发生的;

子View中的监听事件优先级:  OnTouchListener---->OnTouchEvent---->OnLongClickListener(默认返回false)---->OnClickListener,如果从上往下有一层拦截了,后续就无法接受事件进行响应了;

 onTouch和OnClick方法就是在子View在调用view.setOnTouchListener和view.setOnClickListener中重写的方法,onTouch方法也要返回一个Boolean值,和OnTouchEvent类似

onClick和onLongclick的触发是在onTouchEvent回调中,如果onTouchEvent中不覆写super.onTouchEvent而直接return ture(虽然子view中的super.onTouchEvent 确实等于true),那么将不会触发这个view的longclick和click;如果只在onTouchEvent中的Down事件覆写super.onTouchEvent而UP事件直接return true那么将只触发onLongclick,因为onLongClick只关心接受到了Down事件,而Click还得关心有没有接收到UP事件;如果onLongClick中返回了true,那么将不会触发Click事件;

注!! onLongClick不是必定触发,得需要DOWN事件停留足够久才能触发

 像下面这样:首先调用了setOnTouchListener方法,而且返回的是true:

那么打印出来的结果就是:

也就是说onClickListener根本不会被调用,如果自定义ViewC 还重写了OnTouchEvent事件,那么onTouchEvent事件也不会被调用;

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值