一、View的绘制过程
view的绘制需要经过三个过程:measure、layout、draw,即测量、布局和绘制。
下面依次来看一个每个过程的作用以及具体的实现。
1、measure
下面是View中的measure()方法,可以看到只是一个final方法,不可以被重写。那么,对于继承View的子类来说,需要重写的方法是onMeasure(widthMeasureSpec, heightMeasureSpec);
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 特例,暂时不需要考虑
boolean optical = isLayoutModeOptical(this);
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);
}
// Suppress sign extension for the low bytes
// 因为measure是可能要进行多次的,每一次的值都要保存下来
// 将宽高组合成一个长整型作为键值,后面会用到这个键值来保存计算出来的宽高
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
// long long Map,避免自动装箱,节约内存,提高性能
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// Optimize layout by avoiding an extra EXACTLY pass when the view is
// already measured as the correct size. In API 23 and below, this
// extra pass is required to make LinearLayout re-distribute weight.
// 本次测量使用的MeasureSpec和前一次不同
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
// 宽高的模式均为Exactly
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
// 本次测量的size与保留的结果相同
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
// 标志位,是否需要Layout:MeasureSpec发生变化,且(非Exactly或size不同)
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
// forceLayout和needsLayout组合判断是否真的需要measure:比如当前的宽高已经计算过了;Exactly模式,宽高由parent指定,不需要重新计算
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
// 清除PFLAG_MEASURED_DIMENSION_SET的标志位,开始准备测量
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
// RTL环境
resolveRtlPropertiesIfNeeded();
// forceLayout: -1; 已包含键值: 返回index; 未包含键值:负数
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
// sIgnoreMeasureCache低版本使用,每一次Layout都强制重新measure
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
// onMeasure方法,包含了默认实现,可以重写
onMeasure(widthMeasureSpec, heightMeasureSpec);
// 清除mPrivateFlags3的标志位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// 已经计算过,直接使用index获取测量值
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
// 置位PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT, layout时需要重新测量
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
// 测量值未更新,抛出异常
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()");
}
// 置位PFLAG_LAYOUT_REQUIRED,需要Layout
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
// 将前一次测量使用的MeasureSpec保存
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
// 测量结果使用key作为键值保存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
对于View来说,onMeasure()提供了一个默认的实现,其中使用了getDefaultSize()来取得默认的size。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// getSuggestedMinimumWidth(): view自身给出的最小建议值,无背景是mMinWidth, 有背景是mMinWidth和背景宽度中较大值
// getDefaultSize(): 根据父view的模式,给出最终的size
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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:
// 父view不指定大小,直接使用子view的size
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
// 父view给出精确值或者最大值,使用父view指定的size
result = specSize;
break;
2、layout
接下来来看layout的过程:对于单一的view来说,onLayout实际上是一个空实现,其layout的位置实际上在setFrame中被确定了。setFrame会返回一个boolean值,表示布局是否发生了改变。如果布局发生改变,则会获取此view的OnLayoutChangeListener,然后遍历调用onLayoutChange来通知布局发生变化。
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
// PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT被置位,那么这里要重新测量一次
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;
// Optical,特例,暂不考虑,只是改变了setFrame传入值
// setFrame是view进行布局的重要方法
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 如果布局发生了变化,或者mPrivateFlags的标志位PFLAG_LAYOUT_REQUIRED被置位需要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;
}
// 清除标志位PFLAG_LAYOUT_REQUIRED
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
// 获取OnLayoutChangeListener,遍历通知布局发生改变
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);
}
}
}
// 清除标志位PFLAG_FORCE_LAYOUT
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
// 置位标志位PFLAG3_IS_LAID_OUT
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
3、draw
然后是来看draw。draw方法主要是绘制背景前景滚动条等,当前view的实际绘制动作发生在onDraw()方法内。对于View,onDraw()是一个空实现,子类需要重写来进行自己的绘制。
@CallSuper
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 traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
int saveCount;
// 第一步:在canvas上绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
// Fading Edge是一个边缘阴影渐变的效果
// 如果没有fadingEdge的话,跳过25,只进行346即可
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
// onDraw()空实现,需要重写
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
// 遍历绘制子view,view中为空实现,viewGroup提供了默认实现,一般不需要重写
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;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
// 有Fading Edge的情况下,需要进行完成的流程
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// 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;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// 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);
}
综上所述,如果需要写一个继承自View的自定义View,一般需要重写的就是onMeasure()和onDraw()。
ViewGroup的绘制过程
1、measure
ViewGroup本身没有重写onMeasure()方法,因为ViewGroup的子类的布局特性各不相同,难以对其进行一个统一的基本实现,对于子类来说,需要重写onMeasure()来实现自己的测量。
因此,在ViewGroup中的measure过程我们主要看的是其对于子类的测量,主要的方法是measureChildren()、measureChild()和getChildMeasureSpec()这三个。
首先,调用measureChildren方法,对所有的未隐藏的子View进行遍历,然后分别使用measureChild()方法对其进行测量。其中widthMeasureSpec和heightMeasureSpec分别是ViewGroup宽高的MeasureSpec。
而在measureChild()中使用getChildMeasureSpec()方法来计算子View的MeasureSpec,并且直接调用子View的measure()方法来进行测量。
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// 遍历所有的子view,如果不是GONE的话,使用measureChild对子view进行测量
for (int i = 0; i < size; ++i) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根据父view的宽高MeasureSpec,padding,子view的LayoutParams给出的宽高来获取父view对子view的指导宽高
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// 使用获取到的MeasureSpec来对子View进行measure
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 取ViewGroup的mode和size
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// specSize减去padding,子view可以使用的最大size值
// specSize可能为0,0最为最小保留值
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
// ViewGroup的模式为EXACTLY,其size为match_parent或者固定dimen,具体值已经确定
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
// 子view的size也为固定值,使用子view值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
// match_parent,子view希望和ViewGroup的size相等
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
// wrap_content,子View希望显示完整,size由父view决定,模式为AT_MOST,
// 子view的大小不超过ViewGroup
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
// ViewGroup为AT_MOST, warp_content,最大值由ViewGroup的父View决定
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
// 和之前一样,子View的size为固定值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
// 子View希望充满ViewGroup,但是此时ViewGroup的size还没有真正确定
// 因此,使用ViewGroup的size,模式为AT_MOST,只要不超过ViewGroup即可
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
// 这种情况和match_parent一样
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
// 这种情况比较少,一般用在ListView、GridView
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
// 子View给出固定值,使用子Viwe的size,模式为EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
// 后面两种情况都是一样的,因为ViewGroup的实际size还是不确定的
// 所以子View使用ViewGroup当前的size,模式为UNSPECIFIED
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
// 合成MeasureSpec并返回
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
2、layout
ViewGroup的layout()其实和View是相同的,只是多了一个判断。
值得注意的是,ViewGroup的onLayout()是一个抽象方法,也就是说,子类必须要重写onLayout()方法来实现布局逻辑。一般来说,在onLayout()方法中都是遍历子View,然后调用子View的layout()方法来分别进行布局。
@Override
public final void layout(int l, int t, int r, int b) {
// 相比View的layout,只是多了一个判断
// 如果layout call被阻断,或者正在进行布局动画,则置位标志位
// 否则调用super.layout()走正常View layout的逻辑
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
3、draw
之前在View的draw过程中,有说过dispatchDraw()这个方法,因为单个View实际上不存在子View,在View.java中,这个方法是个空实现。在ViewGroup中,实现了此方法,实际上并不需要完全地去关注方法的内容,其中主要还是遍历所有子View,然后使用more |= drawChild(canvas, transientChild, drawingTime);来绘制子View。
未完待改