View的事件体系
View的事件体系
复习复习复习!!!
打开以前的思维导图:
View的事件分发机制
首先事件分发机制分发的是MotionEvent事件,也就是点击事件,是当MotionEvent事件产生以后,系统需要把这个事件传递给一个具体的View并且得到处理的过程。 事件产生后的传递过程是从Activity--->Window--->View的,即隧道式传递,事件产生之后首先传递给 Activity ,Activity 会传递到 PhoneWindow 上,PhoneWindow 会传递给 RootView,而 RootView 其实就是 DecorView ,接下来便是从 DecorView 到 View 上的分发过程了,对于View和ViewGroup的事假分发过程稍有不同:
当事件分发到当前 ViewGroup 的时候,首先会调用它的 dispatchTouchEvent 方法,在 dispatchTouchEvent 方法里面会调用 onInterceptTouchEvent 来判断是否要拦截当前事件,如果要拦截的话,就会调用 ViewGroup 自己的 onTouchEvent 方法了,如果 onInterceptTouchEvent 返回 false 的话表示不拦截当前事件,那么事件会继续往当前 ViewGroup 的子 View 上面传递,如果它的子 View 是 ViewGroup 的话,就继续重复 ViewGroup 事件分发过程,如 果子 View 就是 View 的话,就按照View 的事件分发过程进行传递。
事件分发给View,首先当然也是执行dispatchTouchEvent 方法,但是如果我们为当前 View 设置了 onTouchListener 监听器的话,首先就会执行它的回调方法onTouch,这个方法的返回值会决定事件是否要继续传递下去:如果返回 false,表示事件没有被消费,还会继续传递下去;如果返回 true,表示事件已经被消费了,不需要向下传递了;如果返回 false,那么将会执行当前 View 的 onTouchEvent 方法, 如果我们为当前 View 设置了 onLongClickListener 监听器的话,则首先会执行他的回调方法 onLongClick,和 onTouch 方法类似,如果该方法返回 true 表示事件被消费,不会继续向下传递,返回 false 的话,事件会继续向下传递,假定返回 false,如果我们设置了 onClickListener 监听器的话,则会执行他的回调方法 onClick,该方法是没有返回值的,所以也是我们事件分发机制中最后执行的方法了;可以注意到的一点就是只要你的当前 View 是 clickable 或者 longclickable 的,View 的 onTouchEvent 方法默认都会返回 true,也就是说对于事件传递到 View 上来说,系统默认是由 View 来消费事件的,但 是 ViewGroup 就不是这样了。
上面的事件分发过程是正常情况下的,但是如果有这样的情况,比如事件传递到最里层的 View 之后,调用该 View 的 onTouchEvent 方法返回了 false,那么这时候事件将通过冒泡式的方式向他的父 View 传递,调用它父 View 的 onTouchEvent 方法,如果正好他的父 View 的 onTouchEvent 方法也返回 false 的话,这个时候事件最终将会传递到 Activity 的 onTouchEvent 方法了,也就是最终就只能由 Activity 自己来处理了。
事件分发还需要注意的是: (1)如果说除 Activity 之外的 View 都没有消费掉 DOWN 事件的话,那么事件就不会再传递到 Activity 里面的子 View 了,会直接由Activity调用自己的onTouchEvent方法来处理;(2)一旦一个 ViewGroup 决定拦截事件,那么这个事件序列剩余的部分将不再会由当前ViewGroup 的子 View 去处理了,就是事件将在当前 ViewGroup 层停止向下传递,同时随后的 事件序列也不会再调用 onInterceptTouchEvent 方法了; (3)如果一个 View 开始处理事件但是没有消费掉 DOWN 事件,那么这个事件序列随后的事件也将不再由这个View 来处理,逻辑上讲DOWN事件都没有能够处理再处理其他事件也没有意义了;(4)View的onTouchEvent方法是否执行是和它的onTouchListener回调方法onTouch 的返回值息息相关,如果onTouch 返回 true,那么onTouchEvent 方法就不会去执行;而如果onTouch 返回 false, onTouchEvent 方法执行,然后因为 onTouchEvent 里面会执行 onClick,所以造成了 onClick 是否执行和 onTouch 的返回值有关系。
//Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {//将事件传递给Window进行分发
return true;
}
return onTouchEvent(ev);
}
View的滑动冲突
在自定义 View 的过程经常会遇到滑动冲突问题,一般滑动冲突的类型有三种:(1)外部View和内部View滑动方向不一致;(2)外部 View和内部View滑动方向一致;(3)上述两种情况的嵌套。
要解决滑动冲突,就是利用的事件分发机制,分为外部拦截法和内部拦截法:
外部拦截法的实现思路是事件首先是通过父容器的拦截处理,如果父容器不需要处理该事件的话,则不拦截,将事件传递给子View,如果父容器决定拦截,就在父容器的 onTouchEvent 方法里面直接处理该事件,这种方法符合事件分发机制。具体实现措施是修改父容器的 onInterceptTouchEvent 方法,在达到某一条件的时候,让这个方法直接返回 true 把事件拦截下来进而调用自己的 onTouchEvent 方法来处理,需要注意的是如果想要让子 View 能够收到事件,需要在 onInterceptTouchEvent 方法里面判断如果是 DOWN 事件的话,返回 false,这样后续的 MOVE 以及 UP 事件才有机会传递到子 View 上面,如果你直接在 onInterceptTouchEvent 方法里面 DOWN 情况下返回了 true,那么后续 的 MOVE 以及 UP 事件会直接由当前 View 的 onTouchEvent 处理了,这样的拦截将根本没有意义了,拦截只是在满足一定条件才会拦截,并不是所有情况下都拦截; 内部拦截法实现思路是事件从父容器传递到子View,父容器不做任何干预性措施,所有的事件都传递给子 View,如果子元素需要该事件,那么就 由子元素消耗掉,该事件也就不会回传了,如果子元素不需要该事件,就回传给父容器处理;具体实现重写子View的dispatchTouchEvent方法,并且需要借助requestDisallowInterceptTouchEvent 方法,这个方法用来告诉父容器是否拦截当前事件,为了配合子 View 能够调用这个方法成功,父容器必须默认能够拦截除了 DOWN 事件以外的事件,因为如果一旦父容器拦截了 DOWN 事件,那么后续事件将不会再传递给子元素了,内部拦截法也就失去作用了,同时默认拦截MOVE和UP事件是为了当子View调用parent.requestDisallowInterceptTouchEvent(false)的时候父元素能继续拦截所需事件 ;