前面说到过requestLayout()方法,而这个方法会触发performTraversals()方法
这个方法则开始View的绘制流程,这个方法分别触发performMeasure,performLayout与performDraw方法,完成measure(测量),layout(布局定位),draw(绘制)这三大流程
接下来来分析这三个流程
1.measure测量流程
measure()是final方法,故不能被继承重写,而measure方法会去调用onMeasure方法,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension设置View宽高的测量值
看下getDefaultSize这个方法
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
当MeasureSpec的Model为AT_MOST与EXACTLY时候返回大小值为MeasureSpec的specSize值
当为UNSPECIFIED则使用外面传进来的size
这个外面传进来的size通过getSuggestedMinimumWidth方法获取,具体实现就不看了,主要是View没有设置背景,那么这个值返回android:minWidth这个值所指定的值,如果设置了的话,则返回返回android:minWidth与背景最小宽度中的最大值
上面介绍了View的measure方法,接下来说说关于ViewGroup的测量过程
对于ViewGroup来说除了完成自身的measure,还需要对子View进行measure,而ViewGroup并没有提供onMeasure方法,因为不同的ViewGroup的测量方式不同,但是它提供了measureChildren方法,如下
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
这里首先获取子View的layoutParams,通过getChildMeasureSpec获取子元素的MeasureSpec,然后传递给View的measure方法来进行测量
2.layout流程
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
layout代码如上,主要是确定View自身的位置,onLayout方法则用来确定子元素的位置,所以如果是自定义ViewGroup则需要重写这个onLayout方法
3.draw过程
View绘制流程遵循如下几部
- 1.绘制背景
- 2.绘制自己
- 3.绘制children
- 4.绘制装饰
代码如下
public void draw(Canvas canvas) {
...
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
...
}
到这里基本就简单的介绍了关于View的测量过程,但是我们会有一些疑问,
1.MeasureSpec从哪里来的?
2.View触发绘制的流程?
要理解这两个问题那就要从绘制起始地方看起了就是requestLayout方法,requestLayout方法调用了scheduleTraversals方法,这个方法间接调用了performTraversals()方法,我们看下,这个方法,
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
performDraw();
......
}
由于这个方法太长,我仅仅将我们需要的代码展示出来了
可以看到我们通过getRootMeasureSpec方法获取MeasureSpec,而我们传递给getRootMeasureSpec方法的两个参数mWith和mHeight 是屏幕的宽度和高度, lp是WindowManager.LayoutParams,它的lp.width和lp.height的默认值是MATCH_PARENT
我们再看下getRootMeasureSpec方法如下
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
所以通过getRootMeasureSpec 生成的测量规格MeasureSpec 的mode是MATCH_PARENT ,size是屏幕的高宽。
接下来就是调用performMeasure方法,这个方法其实是调用DecorView的measure方法,而DecorView的是继承FrameLayout的,所以会调用到FrameLayout的measure方法,而这个方法的具体实现则在onMeasure方法中
而FrameLayout的onMeasure方法则会调用measureChildWithMargins方法,从而对子View进行View的measure测量
所以通过这样的解读就知道整个界面的绘制流程了,同时也清楚了关于MeasureSpec的由来