前言
本文章讲解是上一篇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我们可以得出这样的结论:
- 优先级:OnTouchListener > onTouchEvent > OnClickListener
- onTouchEvent的事件处理是通过调用OnClickListener来处理的
- OnTouchListener的返回值影响onTouchEvent进而影响OnClickListener,当返回值为false时不影响,当返回值为true时事件走到OnTouchListener就消费了,不会走到onTouchEvent中调用OnClickListener来消费
- OnTouchListener具有事件消费的能力,返回为true表示消费了此事件
- 猜想:官方提供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
- 事件传递是向下传递,并且传递到最底层组件时,再按照当前组件OnTouchListener > onTouchEvent > OnClickListener的顺序进行消费
- 如果子组件没消费事件则返回给父类进行执行消费,执行顺序也是按照OnTouchListener > onTouchEvent > OnClickListener进行
- 事件传递向下传递的过程中不是先调用父类的OnTouchListener后再调用子view的OnTouchListener!
- 如果viewgroup中通过onInterceptTouchEvent返回true拦截了事件后,这个事件就交给当前的viewgroup按照OnTouchListener > onTouchEvent > OnClickListener顺序进行
流程解析
我们继续分析
- dispatchTouchEvent和onTouchEvent有什么关系?
- onTouchEvent与OnClickListener有什么关系?
- 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
- 事件传递到时候第一步会走到dispatchTouchEvent,如果dispatchTouchEvent方法调用了super.dispatchTouchEvent(ev)方法,则会走到OnTouchListener方法中,根绝OnTouchListener的返回值为true表示消费了事件,事件传递完成,如果返回为false,则会走到onTouchEvent方法里面根据返回值决定事件是否消费
- 事件走到onTouchEvent,如果onTouchEvent调用了super.onTouchEvent(event)方法,则会走到OnClickListener来消费事件!
解决Android的OnTouchListener与OnClickListener冲突
在进行android开发时难免同时会用到onClick和onTouch事件,比如要实现view有click事件,并且通过onTouch实现了跟随手指而移动,在移动完成后不进行人和事情。如果直接使用,在移动完成松手以后也会出现click事件
主要是通过反射获取mListenerInfo实例,再获取mListenerInfo里面的mOnClickListener,最后调用mOnClickListener.onClick()方法即可
网友评论