一.View 的事件分发
(一).概念
1.事件分发
View 的事件分发其实就是点击事件( MotionEvent )从产生后系统开始分发,到传递给一个具体的 View ( 或者Activity )的过程,View (或 Activity )会选择是否对事件进行消耗。
2.事件的类型和事件序列
(1)事件类型
- MotionEvent.ACTION_DOWN 按下时产生的事件
- MotionEvent.ACTION_MOVE 滑动时产生的事件
- MotionEvent.ACTION_UP 抬起时产生的事件
- MotionEvent.ACTION_CANCEL 发生异常时产生的事件
(2)事件序列
同一个事件序列指的是从按下时候到抬起时产生的一系列事件,通常以 DOWN 开始,中间有不定数个 MOVE ,最后以 UP 或者 CANCLE 结束。
3.分发对象和对应的方法
对事件的分发主要涉及三个对象,Activity , ViewGroup ,具体的 View,这三个对象按分发的层次依次是Activity -> ViewGroup -> 具体的 View 。而涉及分发的方法同样主要有三个:
- dispatchTouchEvent 对一个事件进行分发,可能是分发给下一层处理,或者分发给自己。
- onInterceptTouchEvent 这个方法只有 ViewGroup 有,用来判断对事件是否进行拦截,如果拦截就不会分发给下一层.
- onTouchEvent 对事件进行处理,消耗或者不消耗,不消耗就会返回给上层。对于 ViewGroup 和 View 这个方法还受到 OnTouchListener 和 enable 属性 的影响,具体的后面会阐述。
(二).事件分发
1.Activity 对事件的分发
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
Activity 的事件的处理其实并不负责,即如果下层(不管是 ViewGroup 还是 View )消耗了这个事件,那么 if 语句就为 true , 则 dispatchTouchEvent 就返回 true 。如果没有消耗就自己对事件进行处理,即调用 onTouchEvent 方法。1
2
3
4
5
6
7
8public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
1 | /** @hide */ |
Activity 的 onTouchEvent 会对这个事件进行判断,如果事件在窗口边界外就返回 true,dispatchTouchEvent 就返回 true ;如果在边界内就 返回 false ,最后 dispatchTouchEvent 也会返回 false 。这部分流程如图 (这是截自整体流程图的一部分)
2.View 对事件的分发
这里先说 View 对事件的分发是因为 ViewGroup 继承自 View ,ViewGroup 对事件的分发会调用到父类(也就是View )的方法,因此先理清 View 的分发有助于理解。
1 | public boolean dispatchTouchEvent(MotionEvent event) { |
可以看到 View 的事件的处理是先判断 mOnTouchListener !=null 和 View 设置 ENABLED 这两个条件成不成立,不过成立则 调用 onTouch 方法,且如果 onTouch 返回了 true ,那个事件就被消耗 ,View 的 dispatchTouchEvent 就返回 true ; 相反,如果条件不成立或者 onTouch 返回 false ,那么就会执行 View 的 onTouchEvent 方法。
1 | public boolean onTouchEvent(MotionEvent event) { |
1 | public boolean performClick() { |
在 onTouchEvent 方法中,如果 View 是可点击的,比如设置了 onClick 或者 onLongClick ,就会执行 onClick 方法,并且 onTouchEvent 返回 true ,如果是不可点击的就返回 false 。需要注意的是这里的 onTouchEvent 是可以被重写的。如果 onTouchEvent 返回 true 那么 View 的 dispatchTouchEvent 就返回 true ,事件就被消耗,如果 onTouchEvent 返回 false , 那么 dispatchTouchEvent 也返回 false ,这时 事件就交由上层处理,也就是 ViewGroup 。这部分流程如图
3.ViewGroup 对事件的分发
ViewGroup 对事件的分发也是从 dispatchTouchEvent 方法开始的,不同的是 ViewGroup 对了一个对事件进行拦截的方方法 onInterceptTouchEvent 。
1 | public boolean dispatchTouchEvent(MotionEvent ev) { |
1 | private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, |
ViewGroup 首先会判断 onInterceptTouchEvent 表示要不要拦截,这个方法也可以重写设置是否拦截,如果返回 false 就表示不拦截,这个事件就会分发给下一层,如果拦截就会分发给自己,当然如果子 View 不处理这个事件,还是会传到 ViewGroup ,ViewGroup 会调用父类也就是 View 的方法,后面的过程就和 View 对事件的处理是一样的 onTouch ,onTouchEvent , onClock …
这部分的流程如图:
ViewGroup 对事件的拦截 onInterceptTouchEvent 并不是每一次都会调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();//重置标志
}
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
}
可以看到这里有三个条件:ACTION_DOWN 事件,mFirstTouchTarget 变量,FLAG_DISALLOW_INTERCEPT 这个标志。
1.对于 ACTION_DOWN 事件
在判断之前都会调用 resetTouchState 这个方法重新给 FLAG_DISALLOW_INTERCEPT 置位,因此只要是 DOWN 事件,onInterceptTouchEvent 就会调用。
2.对于 mFirstTouchTarget 这个变量
只要有 View 对事件进行处理,那么这个事件的后续事件就会直接交给这个 View ,mFirstTouchTarget 就直接指向 View , 正常情况下不会再询问 ViewGroup 是否拦截。而特殊情况就是下面这个。
3.对于 FLAG_DISALLOW_INTERCEPT
子 View 可以重置这个标志,使得 disallowIntercept 的值改变从而可能会重新 onInterceptTouchEvent 对事件进行拦截。
到这里View 的事件分发的各个流程就已经讲完,最后是一个整体的流程:
二.View 的绘制
(一).概念
1.绘制机制
View 的绘制机制实际上指的是 View 的三大流程
- 测量流程,测量 View 的大小,对应 measure 方法。
- 布局流程,有了 View 的大小后确定 View 的位置,对应 layout 方法。
- 绘制流程,对 View 的颜色,内容等进行绘制,对应 draw 方法。
View 的绘制从 ViewRootImpl 的 performTraversals 开始,首先会调用 performMeasure 方法,在这个方法中会一个 View 会调用 measure 去测量自己的大小,在此之前会调用 onMeasure 去测量子 View 的大小,这样层层调用,最后就会完成整个测量过程,后面的 layout 和 draw 的过程也是大致如此。流程如图
2.MeasureSpec
MeasureSpec 是一个测量规格,在测量一个 View 的时候 从父类计算出来的 MeasureSpec 会传给这个 View ,同时会根据 View 自身的 LayoutParams 属性,也就是指定的一些 MATCH_PARENT, WRAP_CONTENT,xxdp 等属性最终一起决定 View 的大小。
MeasureSpec 是一个 int 值,有32位,高2位代表 Mode ,后30 位Size .之所以将两个值包装在在一个 int 是因为这样可以减少减少对象的分配。而 Mode 表示测量模式,有三种测量模式分为别
- UPSPECIFIED ,未指定,父 View 对子 View 不做任何限制
- EXACTLY,精确,父 View 给子 View 的大小是一个确定的值,为 Size
- AT_MOST,最大,父 View 给子 View 的大小 是一个不确定的值,最大为 Size
具体的计算就在下面的方法中
1 | protected void measureChildWithMargins(View child, |
在父 View 的 MeasureSpec 确定后,会传递给子 View ,子View 就会根据这个 MeasureSpec 和自己的 LayoutParams 属性,计算出自己的 MeasureSpec 和 大小(即 Size),然后就会将这个 MeasureSpec 传递给子View 的 子View, 从而遍历测量完所有的 View。
1 | public static int getChildMeasureSpec(int spec, int padding, int childDimension) { |
子View 的 MeasureSpec 确定就是上述过程,这个过程可用下图表示
理解这个图只需要注意下面几点
- 只要子 View 是 xxdp 的,不管父View 是什么模式,子 View 都是使用自己的指定的大小 childDimension.
- 如果父 View 是 确定的 (EXACTLY),那么 子View 是 MATCH_PARENT 也就是充满父 View 的话,子View 也是确定的 EXACTLY ;如果子 View 是 WRAP_CONTENT,那么子 View 就是不确定的,但是不能超过父 View 的大小,因此子View 就是 AT_MOST 。
- 如果父 View 是不确定的 (AT_MOST ),那么不管子View 是 MATCH_PARENT 还是 WRAP_CONTENT ,子 View 都是不确定的。
- 如果父 View 是未指定的 (UNSPECIFIED),那么子 View 也是未指定的,size 也就没意义,即为 0 。
(二).三个流程
View 的三个流程都都是从 ViewRootImpl 的 performTraversals 开始的,而且都是从 DecorView 开始的,这里就不对具体的情况进行梳理,而是从宏观的角度却分析,三个流程是如果在 ViewGroup 到其中的子View 中进行工作的。
1.measure
1 | private void performTraversals() { |
1 | private static int getRootMeasureSpec(int windowSize, int rootDimension) { |
在上述过程中,因为第一个 measureSpec 的产生总是布满全屏的, 即 measureSpec 是确定的 EXACTLY, size 就为屏幕大小.在 performMeasure 就会开始对 DecorView (也就是一个 ViewGroup ) 进行测量.
1 | private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { |
因为 ViewGroup 继承自 View ,首先看 View 的 measure1
2
3
4
5
6
7
8
9
10public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
}
1 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { |
在 View 中 measure 是一个 final 方法,因此不能被重写,在里面会调用 onMeasure 方法,这个方法就可以被重写,接下看 具体的某一个的 ViewGroup ( FrameLayout )中的这个方法.
1 | @Override |
在 FrameLayout 中的 onMeasure 首先会对去调用 measureChildWithMargins 去计算自己的 MeasureSpec 然后就去测量子 View 的大小,等所有的子 View 测量好了,就会测量自己的的大小.而子 View 会重复这个两个方法,最后完成所有 View 的 测量.这个流程如图所示:
2.layout
1 | private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, |
在 performLayout 会直接 调用 host 的 layout ,这个 host 实际上就是 DecorView ,DecorView 是一个 ViewGroup ,首先看 ViewGroup 的 layout 方法.1
2
3
4
5
6@Override
public final void layout(int l, int t, int r, int b) {
...
super.layout(l, t, r, b);
...
}
1 |
|
在 ViewGroup 中 layout 是一个 final 的方法,在里面会调用父类的layout 方法,也就是 View 的 layout 方法.这里先说明 onLayout 方法,在这里是一个抽象方法,因为不同的 ViewGroup 对子 View 的位置安排是不一样的,因此具体的 onLayout 需要具体的继承类去实现.先看 View 中的 layout 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//setFrame 确定自己的位置
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
....
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//调用 onLayout
onLayout(changed, l, t, r, b);
...
}
首先在 layout 方法中会确定自己的位置,即 left,top,bottom,right 这个四个属性,接着就会调用 onLayout ,如果这是一个 View,那么 onLayout 就是一个空方法,如果这是一个 ViewGroup ,那么在这方法内就会去确定 子View 的位置.比如 FrameLayout 中.1
2
3
4
5
6
7
8
9
10
11
12
13
14@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
...
...
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
这部分的流程如图:
3.draw
draw 的绘制基本就包含五个步骤:
- 绘制背景
- 绘制自己的内容
- 绘制子 View
- 绘制foreground,比如滚动条
- 绘制一些高光
这个过程对于 View 和 ViewGroup 是一样的,只不过 View 中 不会绘制自己的子 View ,因此是个 dispatchDraw(canvas) 在这里是个空方法.具体的流程如图: