!通过阅读Android开发艺术探索整理
重要方法:
dispatchTouchEvent
onInterceptTouchEvent
onTouchEvent
三者关系:dispatchTouchEvent{ if(onInterceptTouchEvent) return onTouchEvent; else return child.dispatchTouchEvent; }
规则:对根ViewGroup,产生点击事件时,dispatchTouchEvent就会调用,如果onInterceptTouchEvent返回true拦截,将事件交给自身的onTouchEvent,反之,传递到子元素,子元素dispatchTouchEvent调用,如此反复直到事件最终被处理。
优先级:onTouchLinstener>onTouchEvent>onClickLinstener;onTouch返回false,onTouchEvent调用;
事件传递顺序:Activity、Window、顶级View
特殊情况:onTouchEvent返回false,事件将传递给父类容器,以此类推,若所有元素都不处理,最终事件传递到Activity,由Activity的onTouchEvent处理
一些结论:
1.事件序列以down开始,数量不定的move,以up事件结束
2.某个View一旦决定拦截,一个事件序列都只能由它来处理,不会再询问onInterceptTouchEvent是否拦截
3.正常情况下,一个事件序列只能由一个View拦截消耗,通过特殊手段可以将本View该处理的onTouchEvent强行传递给其他View
4.某个View一旦开始处理事件,如果不消耗down事件,那么同一事件序列中的其他事件不会再交给该View,将事件重新交给父元素处理,调用onTouchEvent。事件一旦交给一个View处理,必须消耗掉,否则剩下的事件不再交给它
5.如果一个View不消耗除down事件以外的其他事件,那么事件会消失,父元素onTouchEvent不会被调用,可以接收后续事件。最终消失的点击事件会传递给Activity处理。
6.View 没有onInterceptTouchEvent方法,一旦接收到事件传递,onTouchEvent必然会被调用。
7.ViewGroup默认不拦截任何事件,源码中ViewGroup的onInterceptTouch事件默认返回false
8.View的onTouchEvent默认都会消耗事件,返回true.除非不可点击,clickable及longClickable同时为false。默认longClickable都为false,Button的clickable为true,TextView的为false
9.View的enable属性不影响onTouchEvent默认返回值
10.onClick会发生的前提是当前View可点击,并且接收到down和up事件
11.事件传递过程由外向内,总是先交给夫元素,再由父元素分发给子元素,通过requestDisallowInterceptTouchEvent可以在子元素中干预父元素的事件分发过程,down事件除外。
Activity传递到Window,也就是phoneWindow,由phoneWindow传递到顶层View,既decorView,setContView的父View,最终一定会传递到View。
顶层View处理传递流程:
事件到达顶层View,dispatchTouchEvent会调用,如果拦截,返回true,则由ViewGroup的TouchEvent处理,此时如果设置了onTouchLinstener,会调用onTouch,屏蔽掉onToucheEvent,在onTouchEvent中如果设置了onClickLinstener,会调用onClick,如果顶层View不拦截,则事件会传递到它所在的点击事件链上的子View,子View的dispatchTouchEvent会调用。至此,事件已经从顶层传递到了子View,接下来传递过程跟顶层View一直,如此循环,直到事件被处理
滑动冲突:
常见的几种场景:
1.外部滑动方向与内部不一致
2.外部滑动方法与内部一致
3.上面两种情况的嵌套
处理规则:
1.左右滑动时,由外部View拦截,上下滑动时由内部View拦截。通常可根据滑动距离差判断是水平滑动还是上下滑动,也可以滑动路径与水平方向的夹角,特殊的可以用水平与竖直方向的速度差
2.业务制定:如处于某种状态时需要外部相应滑动,另一种状态时内部相应滑动
3.业务突破点
解决方案:
1.外部拦截法
点击事件都先经过父容器拦截处理,如果父容器需要此事件就拦截,不需要就不拦截。
典型逻辑:重写父容器onInterceptTouchEvent: down必须返回false,不拦截,否则后续事件都要由父元素处理,此时事件无法传递到子元素,up返回false因为该事件本事意义不大,move事件根据是否需要当前点击事件决定是否拦截
2.内部拦截法
父容器不拦截任何事件,所有事件都传递到子元素,如果子元素需要就消耗掉,不需要就交父容器处理,需要配合requestDisallowInterceptTouchEvent。
典型逻辑:重写子元素的dispatchTouchEvent,down事件parent.requestDisallowInterceptTouchEvent(true),move事件,当父容器需要此类点击事件时,parent.requestDisallowInterceptTouchEvent(true)。除子元素处理外,父元素也需要默认拦截除down事件外的其他事件。
down事件不受FLAG_DISSALOW_INTERCEPT标记位控制,一旦父元素拦截down事件,那么所有的事件都无法传递到子元素,内部拦截将无效