美文网首页Android面试
Android事件传递分析-OnTouchListener、on

Android事件传递分析-OnTouchListener、on

作者: imkobedroid | 来源:发表于2019-06-27 11:48 被阅读0次

前言

本文章讲解是上一篇Android事件传递分析-传递日志分析的扩展,主要是分析OnTouchListener、onTouchEvent、OnClickListener三者的区别与一些开发场景的解决!布局文件与上一篇一样,由于方便这里不做介绍,可以参考上一篇文章

场景构建

上一篇我们通过了viewgroupA,viewgroupB,viewC,viewD这四个组件讲解了事件的传递流程,现在我们对viewC进行事件的设置,代码如下:

 viewC.setOnTouchListener { v, event ->
            Log.d(LOG_ID_TOUCH, "我是TouchListener")
            when (event.action) {
                0 -> Log.d(LOG_ID_TOUCH, "ACTION_DOWN")
                1 -> Log.d(LOG_ID_TOUCH, "ACTION_UP")
                2 -> Log.d(LOG_ID_TOUCH, "ACTION_MOVE")
                3 -> Log.d(LOG_ID_TOUCH, "ACTION_CANCEL")
            }
            true
        }

 viewC.setOnClickListener {
            Log.d(LOG_ID_TOUCH, "我是onclick")
        }

因为OnTouchListener需要一个返回值,我们这里先返回为false,打印的日志如下:

 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_DOWN
 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_UP
 D/TOUCH点击事件: 我是onclick

这里可以看出我们首先调用的是OnTouchListener,最后回调的是OnClickListener,这里可以得出一个结论,事件的传递中OnTouchListener的优先级是高于OnClickListener的!

我们再将OnTouchListener的返回值设置为true,打印日志如下:

 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_DOWN
 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_UP

这里我们看到设置为true后我们这边的没有回调OnClickListener事件!这里我们先总结一下

总结1:

事件的传递中OnTouchListener的优先级是高于OnClickListener的!并且OnClickListener的调用与OnTouchListener的返回值有关,返回为false时候会调用,返回true的时候不调用!


继续分析OnTouchListener与onTouchEvent关系,先将OnTouchListener的返回值设置为false,因为onTouchEvent是事件处理的,所以这里我们点击viewC把整个事件传递的日志打出,日志如下:


 D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false
 D/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupB onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false
 D/点击事件: ViewC dispatchTouchEvent -> ACTION_DOWN
 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_DOWN
 D/点击事件: ViewC onTouchEvent -> ACTION_DOWN
 D/点击事件: ViewC onTouchEvent return super.onTouchEvent(ev)=true
 D/点击事件: ViewC dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 D/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 D/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 
 .......省略ACTION_UP事件日志,传递与ACTION_DOWN一样........
 
 D/TOUCH点击事件: 我是onclick

分析:
传递过程不做多讲,上篇文章讲的很详细了,事件传递到viewC上第一个接受的是OnTouchListener,然后再传递到onTouchEvent,由于我们做了viewC的OnClickListener消费事件的处理,所以onTouchEvent消费了这个事件,具体消费的过程再日志中也有展示,调用了OnClickListener消费

这里我们可以总结出OnTouchListener的优先级是高于onTouchEvent的,但是OnTouchListener的返回值会不会影响onTouchEvent的调用呢?我们将OnTouchListener的返回值设置为true再打印日志如下:


 D/点击事件: ViewGroupA dispatchTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupA onInterceptTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false
 D/点击事件: ViewGroupB dispatchTouchEvent -> ACTION_DOWN
 D/点击事件: ViewGroupB onInterceptTouchEvent -> ACTION_DOWN
 D/点击事件: ViewC dispatchTouchEvent -> ACTION_DOWN
 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_DOWN
 D/点击事件: ViewC dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 D/点击事件: ViewGroupB dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 D/点击事件: ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= true
 
  .......省略ACTION_UP事件日志,传递与ACTION_DOWN一样........

看到这里证明了我们的猜想是正确的,OnTouchListener当返回为true时,不会回调onTouchEvent方法来消费事件,并且调用了super.dispatchTouchEvent(ev)=true来通知父容器我已经消费了这个事件

总结2

经过上面的分析并联系到总结1我们可以得出这样的结论:

  1. 优先级:OnTouchListener > onTouchEvent > OnClickListener
  2. onTouchEvent的事件处理是通过调用OnClickListener来处理的
  3. OnTouchListener的返回值影响onTouchEvent进而影响OnClickListener,当返回值为false时不影响,当返回值为true时事件走到OnTouchListener就消费了,不会走到onTouchEvent中调用OnClickListener来消费
  4. OnTouchListener具有事件消费的能力,返回为true表示消费了此事件
  5. 猜想:官方提供OnTouchListener就是方便开发者来处理消费事件而不需要重写容器里面的onTouchEvent方法进行处理

继续分析,我们想验证一下,事件传递中是先调用父容器的OnTouchListener还是调用子view的OnTouchListener呢?(当然前提是点击这个子view),我们改变代码如下:

    viewC.setOnTouchListener { v, event ->
            Log.d(LOG_ID_TOUCH, "我是TouchListener")
            when (event.action) {
                0 -> Log.d(LOG_ID_TOUCH, "ACTION_DOWN")
                1 -> Log.d(LOG_ID_TOUCH, "ACTION_UP")
                2 -> Log.d(LOG_ID_TOUCH, "ACTION_MOVE")
                3 -> Log.d(LOG_ID_TOUCH, "ACTION_CANCEL")
            }
            false
        }


        viewGroupB.setOnTouchListener { v, event ->
            Log.d(LOG_ID_TOUCH, "我是viewGroupB的TouchListener")
            when (event.action) {
                0 -> Log.d(LOG_ID_TOUCH, "我是viewGroupB的ACTION_DOWN")
                1 -> Log.d(LOG_ID_TOUCH, "我是viewGroupB的ACTION_UP")
                2 -> Log.d(LOG_ID_TOUCH, "我是viewGroupB的ACTION_MOVE")
                3 -> Log.d(LOG_ID_TOUCH, "我是viewGroupB的ACTION_CANCEL")
            }
            true
        }
    }

我们点击viewC,让事件先传递过去,但是我们不消耗事件,又返回给viewgroupB,日志如下:

 D/TOUCH点击事件: 我是TouchListener
 D/TOUCH点击事件: ACTION_DOWN
 D/TOUCH点击事件: 我是viewGroupB的TouchListener
 D/TOUCH点击事件: 我是viewGroupB的ACTION_DOWN
 D/TOUCH点击事件: 我是viewGroupB的TouchListener
 D/TOUCH点击事件: 我是viewGroupB的ACTION_UP

看到日志我们可以有以下的结论

总结3

  1. 事件传递是向下传递,并且传递到最底层组件时,再按照当前组件OnTouchListener > onTouchEvent > OnClickListener的顺序进行消费
  2. 如果子组件没消费事件则返回给父类进行执行消费,执行顺序也是按照OnTouchListener > onTouchEvent > OnClickListener进行
  3. 事件传递向下传递的过程中不是先调用父类的OnTouchListener后再调用子view的OnTouchListener!
  4. 如果viewgroup中通过onInterceptTouchEvent返回true拦截了事件后,这个事件就交给当前的viewgroup按照OnTouchListener > onTouchEvent > OnClickListener顺序进行

流程解析

我们继续分析

  1. dispatchTouchEvent和onTouchEvent有什么关系?
  2. onTouchEvent与OnClickListener有什么关系?
  3. dispatchTouchEvent与OnTouchListener有什么关系?

首先我们分析问题1,我们修改viewgroupB中的dispatchTouchEvent方法,不调用父类直接返回为true

@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.d(LOG_ID, this.getClass().getSimpleName() + " dispatchTouchEvent -> " + ViewUtils.actionToString(ev.getAction()));
        return true;
    }

打印日志:


 ViewGroupA dispatchTouchEvent -> ACTION_DOWN
 ViewGroupA onInterceptTouchEvent -> ACTION_DOWN
 ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false
 ViewGroupB dispatchTouchEvent -> ACTION_DOWN
 ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= true

我们再修改dispatchTouchEvent方法,不调用父类直接返回为false
打印日志如下:

 ViewGroupA dispatchTouchEvent -> ACTION_DOWN
 ViewGroupA onInterceptTouchEvent -> ACTION_DOWN
 ViewGroupA onInterceptTouchEvent return super.onInterceptTouchEvent(ev)=false
 ViewGroupB dispatchTouchEvent -> ACTION_DOWN
 ViewGroupA onTouchEvent -> ACTION_DOWN
 ViewGroupA onTouchEvent return super.onTouchEvent(ev)=false
 ViewGroupA dispatchTouchEvent return super.dispatchTouchEvent(ev)= false

对面两个日志可以发现其实差别不大可以总结一下

  • 两次都没有调用onTouchEvent方法,说明onTouchEvent是在super.dispatchTouchEvent(ev)中来调用的,并且作用就是来消耗事件的
  • 返回为false时因为没有调用onTouchEvent,所以事件并没有消耗掉,会交给父组件的onTouchEvent来处理

由于前面我们已经分析了onTouchEvent与OnClickListener的关系我们这里就忽略...

用同样的方法打印日志可以证明dispatchTouchEvent与OnTouchListener的关系,这里就不做演示

总结4

  1. 事件传递到时候第一步会走到dispatchTouchEvent,如果dispatchTouchEvent方法调用了super.dispatchTouchEvent(ev)方法,则会走到OnTouchListener方法中,根绝OnTouchListener的返回值为true表示消费了事件,事件传递完成,如果返回为false,则会走到onTouchEvent方法里面根据返回值决定事件是否消费
  2. 事件走到onTouchEvent,如果onTouchEvent调用了super.onTouchEvent(event)方法,则会走到OnClickListener来消费事件!

解决Android的OnTouchListener与OnClickListener冲突

感谢解决Android的onClick与onTouch冲突

在进行android开发时难免同时会用到onClick和onTouch事件,比如要实现view有click事件,并且通过onTouch实现了跟随手指而移动,在移动完成后不进行人和事情。如果直接使用,在移动完成松手以后也会出现click事件

主要是通过反射获取mListenerInfo实例,再获取mListenerInfo里面的mOnClickListener,最后调用mOnClickListener.onClick()方法即可

相关文章

网友评论

    本文标题:Android事件传递分析-OnTouchListener、on

    本文链接:https://www.haomeiwen.com/subject/mlilcctx.html