ViewGroup继承自View,ViewGroup是一个包含View的容器。
接口ViewManager里有addView、updateViewLayout、removeView方法,添加、更新、移除方法。
同时ViewGroup是个抽象类,不能直接使用,常用的子类有LinearLayout、relativeLayout、constrainstLayout、frameLayout、CoordinatorLayout等。
public abstract class ViewGroup extends View implements ViewParent, ViewManager
复制代码
我们自定义View时,往往需要重写onMeasure()、onDraw()
自定义ViewGroup时,往往需要重写onMeasure()、onLayout()、onDraw()
这里我们来分析下View的绘制流程。
ViewRootImpl#performTraversals()
- //执行测量过程,performMeasure() -> view.measure()
- //执行布局过程,performLayout() -> view.layout()
- //执行绘制过程,performDraw() -> view.draw()
private void performTraversals() {
WindowManager.LayoutParams lp = mWindowAttributes;
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); //
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//执行测量过程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//执行布局过程
performLayout(lp, mWidth, mHeight);
//执行绘制过程
performDraw();
}
复制代码
ViewRootImpl#getRootMeasureSpec() 根据
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
复制代码
ViewRootImpl#performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
ViewRootImpl#performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mInLayout = true;
final View host = mView;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
}
复制代码
ViewRootImpl#performDraw()
private void performDraw() {
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
复制代码
ViewRootImpl#draw()
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
复制代码
ViewRootImpl#drawSoftware()
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
final Canvas canvas;
canvas = mSurface.lockCanvas(dirty);
canvas.translate(-xoff, -yoff);
mView.draw(canvas);
return true;
}
复制代码
MeasureSpec
MeasureSpec是View中的一个子类,代表的是测量规格:32位的int类型,高两位是mode,低30位是size
Mode有三种状态:
- UNSPECIFIED :父控件没有给子view任何限制,子View可以设置为任意大小。linearLayout/scrollView (随心所欲)
- EXACTLY:父View有确切的大小,子View (match_parent、dp/px)必须是这个范围内 (固定大小)
- AT_MOST:父View为子view指定了一个范围,在这个范围内,子View可以尽可能的大,wrap_content(受限制的)
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT; // 11 0000000000000(后接30个0)
public static final int UNSPECIFIED = 0 << MODE_SHIFT; // 00 000000000000000(后接30个0)
public static final int EXACTLY = 1 << MODE_SHIFT; // 01 000000000000000(后接30个0)
public static final int AT_MOST = 2 << MODE_SHIFT; // 10 000000000000000(后接30个0)
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode; //二进制的加法
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//得到测量模式
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
//得到测量尺寸
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
}
复制代码
View#measure()
**View#measure()**是final类型,子类不能重写,自定义View/ViewGroup往往重写的是onMeasure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
//如果和父类的optical bounds不一样,则需要更新下widthMeasureSpec和heightMeasureSpec
onMeasure(widthMeasureSpec, heightMeasureSpec);
}
复制代码
View#onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
复制代码
View#setMeasuredDimension() final类型,子类不能重写
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//还是与那个optical bounds有关,如果自己和父类不一致,可能会更新measuredWidth,measuredHeight
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
复制代码
View#setMeasuredDimensionRaw() 给mMeasuredWidth、mMeasuredHeight赋值
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
复制代码
View#getDefaultSize()
measureSpec 是由父类传递给子类的测量规格,int类型,32个bit,高两位是模式,低30是尺寸。
- 如果父类是specMode是unspecified(随你所想):则自己取size
- 如果父类的specMode是at_most(受限)或者exactly(固定尺寸),则自己取specSize
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;
}
复制代码
View#getSuggestedMinimumWidth()
mBackground.getMinumWidth()返回背景图Drawable的原始宽度,若无原始宽度,则为0;
- 注:BitmapDrawable有原始宽度,而ShapeDrawable没有
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
复制代码
View的测量过程总结一下:
根据widthMeasureSpec和heightMeasureSpec
LinearLayout#onMeasure()
前面我们说了View#measure()是final类型,不能被重写,需要通过重写onMeasure()来实现自己的尺寸测量
但是ViewGroup中既没有measure()方法,也没有onMeasure()方法)
因为不同的ViewGroup(LinearLayout、relativeLayout、constrainstLayout、frameLayout)有不同的布局特性,,测量自己大小的方式也不一样,比如说LinearLayout会根据orientation,在垂直方向或者水平方向进行叠加;FrameLayout会取子View里最大的一个作为自己的大小;所以,ViewGroup的子类需要各自实现onMeasure()方法。
这里我们以LinearLayout为例: 先看个图,省略部分细节
LinearLayout#onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
measureHorizontal(widthMeasureSpec, heightMeasureSpec);
}
}
复制代码
LinearLayout#measureVertical()
-
- 遍历所有子View & 测量:measureChildren()
-
- 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值(自身实现)
-
- 存储测量后View宽/高的值:调用setMeasuredDimension()
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
final int count = getVirtualChildCount(); // childCount
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight; //如果有weight属性,需要进行二次测量
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
//测量子View并给子View的widthMeasureSpec/heightMeasureSpec赋值
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
final int childHeight = child.getMeasuredHeight();
final int totalLength = mTotalLength; //h用于存储LinearLayout在竖直方向的高度
mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
lp.bottomMargin + getNextLocationOffset(child)); //每次测量完child会进行叠加
}
setMeasuredDimension(widthSizeAndState | (childState&MEASURED_STATE_MASK),
resolveSizeAndState(maxHeight, heightMeasureSpec,
(childState<复制代码
LinearLayout#measureChildBeforeLayout()
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
复制代码
ViewGroup#measureChildWithMargins()
-
根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec,但是这不是子View最终的measureSpec(前面我们分析的时候还有getDefaultSize())
-
child.measure(childWidthMeasureSpec, childHeightMeasureSpec)
View#measure(widthMeasureSpec, heightMeasureSpec) View.onMeasure(widthMeasureSpec, heightMeasureSpec) setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)) 复制代码
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
复制代码
ViewGroup#getChildMeasureSpec()
根据父View的measureSpec和子View的layoutParams,得到子View的measureSpec
-
如果子view的layout_width是固定的dp/px,则子View的MeasureSpec = exactly + childSize
-
如果子view的layout_width是match_parent
如果父View是exactly,则子View与父View等大,子View的MeasureSpec = exactly + parentSize 如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize 如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 复制代码
-
如果子View的layout_width是wrap_content
如果父View是exactly,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize 如果父View是at_most,则子View也不可能超过父Viw的限定值,子View的MeasureSpec = at_most + parentSize 如果父View是unspecified,则子View想要多大可以有多大,size已经无意义,子View的MeasureSpec = unspecified + 0 复制代码
针对MeasuerSpec = unspecified + 0的状况,会通过child.measure -> getDefaultSize() -> getSuggestedMinumSize()来设定。
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//parentSize是 Math.max(0, specSize - padding) ,padding是所有已用空间+padding+margin
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
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.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
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;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
复制代码
view#layout()
-
View的最终的布局位置和大小完全由mLeft、mTop、mRight、mBottom这4个参数决定,确定了左上角的位置和宽高也就知道四个点的位置。
(mLeft,mTop, mRight, mBottom) (mLeft,mTop, mLeft + getMeasuredWidth(), mTop + getMeasuredHeight()) 复制代码
-
也就是说在layout过程中,通过getMeasuredWidth()、getMeasuredHeight得到view的宽度、高度
getMeasuredWidth() = mMeasuredWidth & MEASURED_SIZE_MASK // mMeasuredWidth的低30位 getMeasuredHeight() = mMeasuredHeight & MEASURED_SIZE_MASK // mMeasuredHeight的低30位 复制代码
-
layout之后可以通过getWidth()、getHeight()得到view宽度、高度
getWidth() = mRight - mLeft getHeight() = mBottom - mTop 复制代码
-
一般来说getMeasuredWidth() == getWidth(),也可以不一样。
public void layout(int l, int t, int r, int b) {
//存储旧值,用于onLayoutChangeListener
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);
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
复制代码
View#setFrame()
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
//用于onSizeChanged的监听
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left; //设置一批新的left、top、right、bottom
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
}
return changed;
}
复制代码
View#onLayout() View没有子View,所以onLayout是个空的实现
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
复制代码
ViewGroup#layout()
final方法,子类不能重写,会调用super.super.layout(l, t, r, b)
@Override
public final void layout(int l, int t, int r, int b) {
super.layout(l, t, r, b);
}
复制代码
ViewGroup#onLayout()
因为不同的ViewGroup具有不同的布局特性,所以定义为抽象方法,所有继承自ViewGroup的都需要重写onLayout()
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
复制代码
LinearLayout#onLayout()
这里仍然以LinearLayout的orientation为vertical的为例
@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);
}
}
复制代码
LinearLayout#layoutVertical()
-
遍历子View,对子View进行摆放。
* 根据android:gravity属性对childTop进行调整 * 根据android:gravity属性对childLeft进行调整 * setChildFrame(child, childLeft, childTop, childWidth, childHeight) 复制代码
void layoutVertical(int left, int top, int right, int bottom) {
int childTop;
int childLeft;
final int count = getVirtualChildCount();
//根据android:gravity属性对childTop进行调整
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
final int childWidth = child.getMeasuredWidth(); //measureSize,所以要先measure再layout
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
//根据android:gravity属性对childLeft进行调整
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
}
}
}
复制代码
View#getMeasureWidth()
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
复制代码
LinearLayout#setChildFrame()
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
复制代码
View#draw()
- 绘制背景
- 绘制view内容
- 绘制children
- 绘制装饰
public void draw(Canvas canvas) {
if (!dirtyOpaque) {
drawBackground(canvas);
}
if (!verticalEdges && !horizontalEdges) {
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
onDrawForeground(canvas);
drawDefaultFocusHighlight(canvas);
}
复制代码
View#onDraw() 自定义View都要重写onDraw,比如说textView/button/imageView啦
protected void onDraw(Canvas canvas) {
}
复制代码
ViewGroup#dispatchDraw()
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < childrenCount; i++) {
more |= drawChild(canvas, transientChild, drawingTime);
}
}
复制代码
ViewGroup#drawChild()
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
复制代码
LayoutParams
ViewGroup.LayoutParams
public static class LayoutParams {
public static final int FILL_PARENT = -1;
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
public int width;
public int height;
public LayoutParams(int width, int height) {
this.width = width;
this.height = height;
}
}
复制代码
ViewGroup.MarginLayoutParams
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
public int leftMargin;
public int topMargin;
public int rightMargin;
public int bottomMargin;
private int startMargin = DEFAULT_MARGIN_RELATIVE;
private int endMargin = DEFAULT_MARGIN_RELATIVE;
}
复制代码
LinearLayout.LayoutParams
ViewGroup的继承类都有其各自不同的特性,LayoutParams就是它们的属性特征。
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public float weight;
public int gravity = -1;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
TypedArray a =
c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);
gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);
a.recycle();
}
}
复制代码
参考:
Android View的绘制流程