1、前言
在Android进阶知识:绘制流程(上)中主要是关于绘制流程中会遇到的基础知识,这一篇开始来看具体View
绘制的流程。前篇中讲过View
分为ViewGroup
和一般的View
,ViewGroup
中可以包含其他View
或ViewGroup
,并且ViewGroup
继承了View
,所以绘制流程中ViewGroup
相比一般View
除了要绘制自身还要绘制其子View
。View
的绘制流程分为三个阶段:测量measure
、布局layout
、绘制draw
,接下来就开始对View
和ViewGroup
绘制流程的这三个阶段一个个开始研究。
2、View的绘制流程
2.1 View的measure流程
View
的测量流程从View
的measure
方法开始,所以先来看它的measure
方法。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//optical用来判断是否使用了光学边界布局的ViewGroup
boolean optical = isLayoutModeOptical(this);
//判断当前View的mParent是否与自己一样使用了光学边界布局的ViewGroup
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
//生成一个key作为测量结果缓存的key
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//缓存为空就new一个新的LongSparseLongArray
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//是否强制layout
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
//上次测量和这次结果是否相同
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
//宽高测量模式是否皆为EXACTLY模式
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
//MeasureSpec中的size测量大小是否相等
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
//是否需要layout
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//判断是否需要layout或者强制layout
if (forceLayout || needsLayout) {
//清除已测量的flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
//测量之前还有先判断缓存如果是强制layout则为-1,否则去缓存中根据之前生成的key找对应的value
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//如果cacheIndex小于0即强制layout或者忽略缓存
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
//就调用onMeasure方法测量宽高
onMeasure(widthMeasureSpec, heightMeasureSpec);
//onMeasure测量结束后再修改状态flag
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
//否则就用缓存中的值
long value = mMeasureCache.valueAt(cacheIndex);
//通过setMeasuredDimensionRaw方法将缓存值赋给成员变量
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
//这里对复写onMeasure方法却未调用setMeasuredDimension方法做了校验抛出异常
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//更新mOldWidthMeasureSpec,mOldHeightMeasureSpec
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//更新缓存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
复制代码
注释已经很详细了,这里再梳理总结一下measure
方法做了哪些事。其实measure
方法主要功能就是调用onMeasure
方法测量大小,但是不是每次都调用,这里做了优化。先要判断是否是需要layout
或者是强制layout
。判断通过之后还要再判断是否是强制测量或者忽略缓存,如果强制测量或者忽略缓存才会去调用onMeasure
方法区测量,否则直接用缓存中上一次测量的缓存就行。
接下来看onMeasure
测量方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
复制代码
onMeasure
方法里就调用了setMeasuredDimension
方法。
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
复制代码
setMeasuredDimension
方法里又调用了setMeasuredDimensionRaw
方法,这里就和之前从缓存中取数据调用方法是一样的了,这里将测量好的宽高赋给成员变量。回头再看onMeasure
方法里的setMeasuredDimension
方法传参,这里传参都先调用了getDefaultSize
方法,而getDefaultSize
方法又调用了getSuggestedMinimumWidth
和getSuggestedMinimumHeight
方法。一个一个来看,这里Width
和Height
方法是类似的,所以就看getSuggestedMinimumWidth
方法即可。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
复制代码
这个方法里判断了View
是否有mBackground
,没有背景就返回View
的最小宽度,有背景就从View
的最小宽度和背景的最小宽度中选较大的返回。这里的最小宽度可以在xml
里通过相关属性设置或者调用setMinimumWidth
方法设置。
"match_parent"
android:layout_height="match_parent"
android:minWidth="100dp"
android:minHeight="200dp"
/>
复制代码
public void setMinimumWidth(int minWidth) {
mMinWidth = minWidth;
requestLayout();
}
复制代码
接下来看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;
}
复制代码
这里传递的参数size
就是getSuggestedMinimumWidth
方法获得View
自身想要的大小,measureSpec
是View
的父布局的MeasureSpec
信息。然后根据父布局MeasureSpec
信息中的specMode
和specSize
来返回测量结果result
。父布局specMode
为UNSPECIFIED
即不限制大小所以result
就为View
想要的大小size
。specMode
为AT_MOST
或者EXACTLY
,则都返回父布局的specSize
大小.
从这里就可以看出View
中默认AT_MOST
或者EXACTLY
模式返回测量大小一样的,所以在自定义View
的时,单单只继承了View
之后,默认宽高设置对应的MATCH_PARENT
和WRAP_CONTENT
属性返回的结果也是一样的,即默认的View
中WRAP_CONTENT
是无效的,需要我们自己复写onMeasure
方法设置AT_MOST
模式下的最小测量值。至此View
的measure
流程就结束了,下面用一张流程图来梳理下View
的测量流程。
2.2 View的layout流程
接下来看View
的布局流程,从layout
方法开始。
public void layout(int l, int t, int r, int b) {
//先判断在layout之前是否需要先调用onMeasure测量
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;
//这里还是根据isLayoutModeOptical方法判断mParent是否使用光学边界布局,分别调用setOpticalFrame和setFrame方法
//setOpticalFrame方法中计算Insets后同样调用了setFrame方法
//最终返回一个布尔值changed表示布局是否发生变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//如果发生了改变或者需要进行layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//就调用onLayout方法
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
//layout之后调用onLayoutChange方法
//获取ListenerInfo
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
//获取ListenerInfo不为空且mOnLayoutChangeListeners就克隆一份OnLayoutChangeListener集合
ArrayList listenersCopy =
(ArrayList)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
//循环调用OnLayoutChangeListener的onLayoutChange方法
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
final boolean wasLayoutValid = isLayoutValid();
//修改强制layout的flag
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
//最后是一些关于焦点的处理
if (!wasLayoutValid && isFocused()) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
if (canTakeFocus()) {
//如果自身能获取焦点就清除父母的焦点
clearParentsWantFocus();
} else if (getViewRootImpl() == null || !getViewRootImpl().isInLayout()) {
//ViewRootImpl为空或者ViewRootImpl不在layout
//这里是一种奇怪的情况,可能是用户而不是ViewRootImpl调用layout方法,最安全的做法在这里还是清除焦点
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
clearParentsWantFocus();
} else if (!hasParentWantsFocus()) {
// original requestFocus was likely on this view directly, so just clear focus
clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
// otherwise, we let parents handle re-assigning focus during their layout passes.
} else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) != 0) {
mPrivateFlags &= ~PFLAG_WANTS_FOCUS;
View focused = findFocus();
if (focused != null) {
// Try to restore focus as close as possible to our starting focus.
if (!restoreDefaultFocus() && !hasParentWantsFocus()) {
focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false);
}
}
}
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
复制代码
总结一下layout
方法中最主要的就是调用setFrame
方法返回一个布局是否发生变化的结果,判断发生了变化就会调用onLayout
方法重新计算布局位置,其它还有一些特殊情况的处理、监听的调用和焦点的处理。下面就来看setFrame
方法。
protected boolean setFrame(int left, int top, int right, int bottom) {
//默认change为false
boolean changed = false;
if (DBG) {
Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
//比较新旧left、top、right、bottom
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
//只要有一个不相等就说明有改变
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
//分别计算旧的宽高和新的宽高
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
//新旧有不相等sizeChanged就为true
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
//更新新的left、top、right、bottom的值
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
//设置更新视图显示列表
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
//如果发生了改变调用sizeChange方法
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
//如果是可见的,就强制加上PFLAG_DRAWN状态位
//防止在调用setFrame方法前PFLAG_DRAWN状态为被清除
//保证invalidate方法执行
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
mDefaultFocusHighlightSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}
复制代码
setFrame
方法中会比较新旧mLeft
、mTop
、mRight
、mBottom
,这四个值就是第一篇文章中提到的View
的四个get
方法获取的值,表示自己相对于父布局的位置。只要有一个不同就说明发生了变化,然后调用mRenderNode.setLeftTopRightBottom
方法设置更新视图显示列表,返回change结果为true
。接下来进入查看onLayout
方法。
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
复制代码
View
的onLayout
方法是一个空方法。因为单个普通View
是没有子View
的,他自身的位置计算在layout
方法中已经计算好了。如果是ViewGroup
则必需要复写onLayout
方法,根据自身要求设置子View
的位置。
2.3 View的draw流程
同样先从View
的draw
方法开始阅读。
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
/*
* draw遍历执行的几个步骤:
*
* 1. 绘制背景
* 2. 如果有必要,保存Canvas图层
* 3. 绘制内容
* 4. 绘制子元素
* 5. 如果有必要,绘制边缘和恢复图层
* 6. 绘制装饰 (例如滚动条scrollbars)
*/
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
......
// Step 2, save the canvas's layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
......
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
......
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
......
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
复制代码
View
中的draw
方法注释和逻辑就比较清晰了,一共主要分为六个步骤绘制:
- 绘制背景
- 如果有必要,保存Canvas图层
- 绘制内容
- 绘制子元素
- 如果有必要,绘制边缘和恢复图层
- 绘制装饰 (例如滚动条scrollbars)
首先是drawBackground
方法:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
//设置背景边界
setBackgroundBounds();
......
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
//直接绘制背景
background.draw(canvas);
} else {
//如果 mScrollX 和 mScrollY 有值 则将画布canvas平移
canvas.translate(scrollX, scrollY);
//之后在调用draw方法绘制背景
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
复制代码
是drawBackground
方法中主要是做了两个操作,一个是设置背景边界,另一个是绘制背景。如果有mScrollX
和mScrollY
有值说明有发生滚动,就先移动画布之后再进行绘制。接下来看一下onDraw
方法:
protected void onDraw(Canvas canvas) {
}
复制代码
和onLayout
一样也是一个空方法,不同View
需要根据需求实现不同内容的绘制。再下来是dispatchDraw
方法。
protected void dispatchDraw(Canvas canvas) {
}
复制代码
dispatchDraw
同样还是空方法,还是因为单独普通的View
是没有子View
的所以不需要绘制子View
。最后来看onDrawForeground
方法绘制装饰。
public void onDrawForeground(Canvas canvas) {
//绘制滚动指示器
onDrawScrollIndicators(canvas);
//绘制滚动条
onDrawScrollBars(canvas);
final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}
final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}
//绘制前景
foreground.draw(canvas);
}
}
复制代码
onDrawForeground
方法中主要是绘制了滚动指示器、滚动条和前景相关内容。View
的draw
流程就是这些,流程图如下。
3、ViewGroup的绘制流程
3.1 ViewGroup的measure流程
ViewGroup
的测量流程的起始和一般的View
是一样的,也是从measure
开始,但是ViewGroup
类中并没有复写measure
方法,所以调用的也就是其父类View
中的measure
方法,所以这一部分ViewGroup
与普通View
是相同的。之后我们知道在measure
方法中会调用onMeasure
方法进行测量。但是默认ViewGroup
类中依旧没有复写onMeasure
方法,这是因为每个ViewGroup
的特性不同需求不同,需要根据要求自己复写onMeasure
方法。onMeasure
方法的复写逻辑是这样的:首先在onMeasure
方法中需要测量每个子View
的大小,接着计算所有子View
总的大小,最后通过setMeasuredDimension
方法,将得到的总大小设置保存到成员变量中,完成测量。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
//所有子View数组
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
//测量每个子View大小,传入子View和父布局大小
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
复制代码
ViewGroup
提供有measureChildren
这个用来测量子View
的方法。在measureChildren
方法中获得了所有子View
的数组,然后调用measureChild
方法,测量每个子View
。
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
//获得子View的LayoutParams
final LayoutParams lp = child.getLayoutParams();
// 根据父布局的MeasureSpec和子View的LayoutParams,计算子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
//调用子View的measure方法
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
measureChild
方法中获取了子View
的LayoutParam
布局参数。然后通过getChildMeasureSpec
方法传入父布局的MeasureSpec
和子View
的LayoutParams
获取到子View
的MeasureSpec
,最后调用了子View
的measure
方法,完成最后的测量。那么下面进入getChildMeasureSpec
方法,看一下实现。
/**
* 这三个参数分别为:
* spec:父布局的MeasureSpec
* padding:内边距
* childDimension:子View的宽高尺寸
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//获取父布局的specMode和specSize
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//计算父布局大小:用父布局的specSize减去内边距
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
//根据父布局的specMode判断
switch (specMode) {
case MeasureSpec.EXACTLY:
//父布局的specMode为EXACTLY时
if (childDimension >= 0) {
//childDimension大于0即子View的宽或高有具体的值
//返回的大小就为childDimension大小,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子View宽或高为MATCH_PARENT
//返回的大小就为父布局大小,mode为EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子View宽或高WRAP_CONTENT
//返回的大小就由子View自己确定,最大不能超过父布局大小,mode所以为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父布局的specMode为AT_MOST时
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//childDimension大于0即子View的宽或高有具体的值
//返回的大小就为childDimension大小,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子View宽或高为MATCH_PARENT
//返回的大小就为父布局大小,mode为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子View宽或高WRAP_CONTENT
//返回的大小就由子View自己确定,最大不能超过父布局大小,mode所以为AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
//父布局的specMode为UNSPECIFIED时
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
//childDimension大于0即子View的宽或高有具体的值
//返回的大小就为childDimension大小,mode为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//如果子View宽或高为MATCH_PARENT
//返回的大小根据View.sUseZeroUnspecifiedMeasureSpec判断,mode为UNSPECIFIED
//sUseZeroUnspecifiedMeasureSpec是View类的成员变量由targetSdkVersion版本决定
//sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < Build.VERSION_CODES.M
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子View宽或高为WRAP_CONTENT
//返回的大小根据View.sUseZeroUnspecifiedMeasureSpec判断,mode为UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//调用makeMeasureSpec方法创建一个MeasureSpec返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
从这个方法可以看出来一个子View
的MeasureSpec
是由其父布局的MeasureSpec
和自身的LayoutParams
共同决定的,根据不同的组合最终返回不同的结果。在测量完所有的子View
之后,需要实现计算ViewGroup
总大小,最后调用setMeasuredDimension
方法存储测量的结果就可以了。ViewGroup
的measure
流程图如下。
3.2 ViewGroup的layout流程
和单个普通View
一样,ViewGroup
的layout
流程同样从layout
方法开始。
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
mLayoutCalledWhileSuppressed = true;
}
}
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
复制代码
ViewGroup
中复写了layout
方法,添加了一些动画过度效果的判断,核心还是调用了super.layout
方法,继而会调用onLayout
方法,onLayout
方法是一个抽象方法,这是因为ViewGroup
同样要根据自身要求特性实现不同的layout
逻辑,所以需要由不同的ViewGroup
自身来实现onLayout
方法。接下来看一下具体的一个ViewGroup
中的onLayout
方法,LinearLayout
中的onLayout
方法。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
复制代码
根据不同方向分为两个方法,这里来看layoutVertical
方法:
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// 计算子View最右的位置
final int width = right - left;
int childRight = width - mPaddingRight;
// 子View的空间
int childSpace = width - paddingLeft - mPaddingRight;
// 获得子View个数
final int count = getVirtualChildCount();
......
// 循环遍历子View
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
// 可见性不为GONE的子View获得其宽高
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
// 获得子View的LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
......
//计算topMargin
childTop += lp.topMargin;
//调用setChildFrame方法,该方法中调用了子View的layout方法来确定位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
// childTop增加子View的高度和bottomMargin,使下一个子View垂直在下方放置
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
复制代码
这里省略了Gravity
属性的相关代码,来看主要的流程,首先是去除内边距计算好子View
的放置空间,然后遍历子View
获得子View
的宽高,再通过setChildFrame
方法确定位置,然后递增childTop
,这样接计算好了子View
的位置,使子View
一个个垂直排列下去。LinearLayout
的onLayout
方法主要实现方法就是这样,其他不同的ViewGroup
也都实现了自己的onLayout
方法。
3.3 ViewGroup的draw流程
ViewGroup
绘制流程同样会调用View
的draw
方法,同样会绘制背景,绘制自身内容等,不同的是dispatchDraw
方法,View
中该方法是一个空方法,但是ViewGroup
中就需要复写这个方法,来看ViewGroup
中的dispatchDraw
方法。
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
//获得所有子View个数和子View数组
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
......
//循环遍历子View
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
// 调用drawChild方法绘制子View
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}
......
}
复制代码
dispatchDraw
方法里核心逻辑还是循环遍历了子View
,然后调用了drawChild
绘制子View
,而drawChild
中又调用了子View
的draw
方法绘制子View
。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
复制代码
ViewGroup的draw流程
4、总结
这一篇主要讲了View
和ViewGroup
的绘制流程,无论View
和ViewGroup
都经历了measure
、layout
、draw
这三个阶段。之间主要的区别就是View
没有子View
的问题,而ViewGroup
的绘制流程中还需要调用其子View
的对应流程,完成子View
的绘制。那么在了解了View
和ViewGroup
的绘制流程之后,新的问题又来了。View
树中,子View
的了measure
、layout
、draw
方法是由其父级的ViewGroup
调用的,父ViewGroup
绘制又是由它的父级调用的,那么根节点的绘制流程是由谁调用开启的呢?绘制好的界面又是怎样显示的呢?最后一篇文章就来看看这些内容。