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(); } }复制代码
参考: