一 点击事件的传递规则
当一个 MothinEvent 产生了以后,系统需要把这个事件传递给一个具体的 View,而这个传递过程就是分发过程,由三个方法共同完成:
dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前 View ,那么此方法一定被调用,返回结果受当前 View 的 onTouchEvent 和下级的 dispatchTouchEvent 方法影响,表示是否消耗该事件。onInterceptTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,判断是否拦截某个事件,如果 View 拦截了某个事件,那么在同一个事件序列中,此方法不会被再次调用,返回结果表示是否拦截该事件。onTouchEvent(MotionEvent event)
在 dispatchTouchEvent 方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前 View 无法再次接收到该事件。上述三者关系,如下伪代码:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)){ //是否拦截 consume = onTouchEvent(ev); //拦截调用 onTouchEvent }else { consume = child.dispatchTouchEvent(ev); //不拦截分发给子 View } return consume; }
事件分发优先级:
onTouch > onTouchEvent > onClick;
事件分发过程:
点击事件产生后,它的传递有如下顺序:Activity -> Window -> View , 即事件总是先传递给 Activity,Activity 再传递给 Window ,最后 Window 再传递给顶级 View,顶级的 View 接收到事件后,就会按照事件分发机制去分发事件。
如果一个 View 的 onTouchEvent 返回 false,那么他的父容器的 onTouchEvent 会调用,如果都不处理这个事件,那么最后会传递到Activity的 onTouchEvent 处理。
结论:
- 1.事件序列:是手指接触到屏幕那一刻起,到手指离开屏幕的那一刻起,这个过程从 down 事件开始,中间有数量不定的 move 事件,最终以 up 事件结束。(down -> move ...move->up)
- 正常情况一个事件序列只能被一个 View 拦截消耗,即同一个事件交给一个 View 处理。但可以通过一个 View 的onTouchEvent 强行传递给其他 View 处理。
- 某个 View 一旦决定拦截,这个事件序列只能交给它处理,他的
onInterceptTouchEvent()
不再调用。
- 某个 View 一旦开始处理事件,如果它不消耗 ACTION_DOWN 事件(onTouchEvent 返回 false),他会将这个事件从新交给他的父元素处理,即父容器的 onTouchEvent 会被调用。一旦交给一个View处理,那么它就必须消耗掉,否则同一事件序列剩下的事件就不再交给它处理了。
- 如果 View 只消耗了 ACTION_DOWN 事件,那么这个点击事件会消失,父容器的 onTouchEvent 并不会调用,并且当前的 View 可以持续收到后续的事件,最终这些消失的点击事件会传递给 Activity 处理。
- ViewGroup 默认不拦截任何事件,Android 源码中 ViewGroup 的 onInterceptTouchEvent 方法默认返回 false。
- View 的 onTouchEvent 默认都是消耗事件(返回true),除非它是不可点击的 >(clickable 和 longClickable 同时为 false)。
View 的 longClickable属性 :默认都为 false。
clickable 属性:要分情况讨论,比如 Button 的 clickable 为 ture,TextView 的 clickable 属性>为 false 。
- View 中没有 onInterceptTouchEvent 方法,一旦有事件传递给它,那么它的 onTouchEvent 方法就会被调用。
- View 的 enable 属性不影响 onTouchEvent 的默认返回值,假如 View 是 disable 状态的,只要它的 clickable 和 longClickable 有一个为 true,那么它的 onTouchEvent 就返回 true。
- onClick 会发生的前提是 View 是可点击的,并且它收到了 down 和 up 的事件。
- 事件传递过程是由外向内的,即事件总先传递给父元素,然后再由父元素分给子 View,通过
requestDisallowInterceptTouchEvent
方法可以在子元素中干预父元素的事件分发。(ACTION_DOWN 事件除外)
二 View 的绘制流程
View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始的,performTraversals 会依次调用 performMeasure ,performLayout,performDraw 三个方法分别完成顶级 View 的measure,layout,draw 这三大流程,
其中在 performMeasure 中会调用 measure 方法,measure 方法会调用 onMeasure 方法,在 onMeasure 中会完成子元素进行 measure 过程,子元素也会重复父元素的这个过程,performLayout,performDraw 同理。
三 理解 MeasureSpec
在测量过程中,系统会将 View 的 LayoutParams 根据父容器所施加的规则转换成对应的 MeasureSpec ,然后根据这个 MeasureSpec 测量出 View 的宽/高。
- MeasureSpec 代表一个 32 位 int 值,高 2 位代表 SpecMode ,低 30 位代表 SpecSize。
SpecMode:测量模式。
SpecSize:某种测量模式下规格的大小。- MeasureSpec 将 SpecMode与SpecSize 打包成一个 int 值,避免过多的内存分配,而 MeasureSpec 值可以解包成 SpecMode与SpecSize 。(MeasureSpec 这里指的 int 值)
- SpecMode有三类:
UNSPECIFIED(未指明):父容器不对 View 有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量状态。
EXACTLY(恰好的):父容器已经检测出 View 所需要的大小,这个时候 View 的最终大小就是 SpecSize 所指定的值,它对应 LayoutParams 中的 match_parent 和具体数值的两种模式。
AT_MOST(至多的):父容器指定了一个大小可用的 SpecSize ,View 的大小不能大于这个值,具体是什么值看 View 的具体实现,它对应 LayoutParams 的 wrap_content。
决定因素:值由子View的布局参数LayoutParams和父容器的MeasureSpec值共同决定。具体规则见下图: