事件分发
本文由其它几个博客简要及自己理解修改增加一些内容而成,还不完善,后续再完善。事件最Activity开始, 然后依次向下传递,最后传给View.大致如下:
Activity -> PhoneWindow -> DecorView -> ViewGroup -> ... -> View
如果没有任何View消费掉事件,那么这个事件会按照反方向回传,最终传回给Activity,如果最后 Activity 也没有处理,本次事件才会被抛弃。如何View消费掉事件,哪么回传true,告知上层。回传如下:
Activity <- PhoneWindow <- DecorView <- ViewGroup <- ... <- View
其实这就是一种责任链模式, 如果自己能处理就拦截下来自己干,如果自己不能处理或者不确定就交给责任链中下一个对象。
自己可以直接拦截该事件,自己处理,也可以先询问(分发给)子View,如果子View需要就交给子View处理,如果子View不需要还能继续交给上层View处理。既保证了事件的有序性,又非常的灵活。
看到上面流程哪么DecorView和 PhoneWindow是做什么的?
要了解 PhoneWindow 是干啥的,首先要了解啥是 Window ,看官方说明:
Abstractbase class for a top-level window look and behavior policy. An instance of thisclass should be used as the top-level view added to the window manager. Itprovides standard UI policies such as a background, title area, default keyprocessing, etc.
简单来说,Window是一个抽象类,是所有视图的最顶层容器,视图的外观和行为都归他管,不论是背景显示,标题栏还是事件处理都是他管理的范畴,但它是抽象类不能直接使用。PhoneWindow 作为 Window 唯一实现类,Window的工作都是PhoneWindow来完成。
DecorView 是 PhoneWindow 的一个内部类,为PhoneWindow 服务的,除了自己的一些特性外,也负责消息的传递。屏幕上没View遮挡的部分会显示主题的颜色,标题的显示等存放在DecorView中。
由于我们无法直接操作 PhoneWindow 和 DecorView ,以下均省略了 PhoneWindow 和 DecorView。
事件分发、拦截与消费
√
表示有该方法。
X
表示没有该方法。
类型 | 相关方法 | Activity | ViewGroup | View |
---|---|---|---|---|
事件分发 | dispatchTouchEvent | √ | √ | √ |
事件拦截 | onInterceptTouchEvent | X | √ | X |
事件消费 | onTouchEvent | √ | √ | √ |
这个三个方法均有一个 boolean(布尔) 类型的返回值,通过返回 true 和 false 来控制事件传递的流程。
PS: 从上表可以看到 Activity 和 View 都是没有事件拦截的,这是因为:
Activity 作为原始的事件分发者,如果 Activity 拦截了事件会导致整个屏幕都无法响应事件,这肯定不是我们想要的效果。
View最为事件传递的最末端,要么消费掉事件,要么不处理进行回传,根本没必要进行事件拦截。
public boolean dispatchTouchEvent(MotionEventevent) --分发事件
作用是用来进行事件的分发。一般在这个方法里必须写 returnsuper.dispatchTouchEvent 。如果不写super.dispatchTouchEvent,而直接改成return true 或者 false,则事件传递到这里时便终止了,既不会继续分发也不会回传给父元素。
public boolean onInterceptTouchEvent(MotionEvent event)--拦截事件
只有ViewGroup才有这个方法。View只有dispatchTouchEvent和onTouchEvent两个方法。因为View没有子View,所以不需要拦截事件。而ViewGroup里面可以包裹子View,所以通过onInterceptTouchEvent方法,ViewGroup可以实现拦截,拦截了的话,ViewGroup就不会把事件继续分发给子View了,也就是说在这个ViewGroup中的子View都不会响应到任何事件了。onInterceptTouchEvent 返回true时,表示ViewGroup会拦截事件。
public boolean onTouchEvent(MotionEvent event)--消费事件
onTouchEvent 返回true时,表示事件被消费掉了。一旦事件被消费掉了,其他父元素的onTouchEvent方法都不会被调用。如果没有人消耗事件,则最终当前Activity会消耗掉。则下次的MOVE、UP事件都不会再传下去了。
注意:
-
一般我们在自定义ViewGroup时不会拦截Down事件,因为一旦拦截了Down事件,那么后续的Move和Up事件都不会再传递下去到子元素了,事件以后都会只交给ViewGroup这里。
-
一个Down事件分发完了之后,还有回传的过程。因为一个事件分发包括了Action_Down、Action_Move、Action_Up这几个动作。当手指触摸到屏幕的那一刻,首先分发Action_Down事件,事件分发完后还要回传回去,然后继续从头开始分发,执行下一个Aciton_Move操作,直到执行完Action_Up事件,整个事件分发过程便到此结束。
总结
一般来说dispatchTouchEvent默认, ViewGroup消费事件时ViewGroup的onInterceptTouchEvent(),return true,拦截事件,不会拦截Down事件,因为一旦拦截了Down事件,那么后续的Move和Up事件都不会再传递下去到子元素了,事件以后都会只交给ViewGroup这里。ViewGroup的onTouchEvent(),return true, 处理事件。
另有:requestDisallowInterceptTouchEvent(true)请求父类不要拦截事件。