参考链接
Android onMeasure、Measure、measureChild、measureChildren 一些简要说明Android layout、onLayout 一些简要说明Android draw、onDraw、dispatchDraw、invalidate、computeScroll 一些简要说明一、measure 测量
1、测量流程

测量View是在measure()方法中,而measure()方法是final修饰的,不允许重写,但是在measure()方法中回调了onMeasure()方法,所以我们自定义View的时候需要重写onMeasure()方法,在该方法中实现测量的逻辑
如果是普通View,则直接通过setMeasureDimension()方法设置大小即可如果是ViewGroup,则需要循环遍历所有子View,调用子View的measure()方法,测量每个子View的大小,等所有的子View都测量完毕,最后通过setMeasureDimension()设置ViewGroup自身的大小2、measure
/** * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { ... onMeasure(widthMeasureSpec, heightMeasureSpec); ... }measure是final修饰的方法,不可被重写。在外部调用时,直接调用view.measure(int wSpec, int hSpec)。measure中调用了onMeasure。自定义view时,重写onMeasure即可
3、onMeasure
PRotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }如果是一个View,重写onMeasure时要注意:如果在使用自定义view时,用了wrap_content。那么在onMeasure中就要调用setMeasuredDimension,来指定view的宽高。如果使用的fill_parent或者一个具体的dp值。那么直接使用super.onMeasure即可。
如果是一个ViewGroup,重写onMeasure时要注意:首先,结合上面两条,来测量自身的宽高。然后,需要测量子View的宽高。测量子view的方式有:
getChildAt(int index),可以拿到index上的子view。通过getChildCount得到子view的数目,再循环遍历出子view。接着,subView.measure(int wSpec, int hSpec),使用子view自身的测量方法
或者调用viewGroup的测量子view的方法:
//某一个子view,多宽,多高, 内部减去了viewGroup的padding值measureChild(subView, int wSpec, int hSpec); //所有子view 都是 多宽,多高, 内部调用了measureChild方法measureChildren(int wSpec, int hSpec);//某一个子view,多宽,多高, 内部减去了viewGroup的padding值、margin值和传入的宽高wUsed、hUsed measureChildWithMargins(subView, intwSpec, int wUsed, int hSpec, int hUsed); Tips:自定义ViewGroup的时候,通常继承FrameLayout,这样就不必实现onMeasure()方法,让FrameLayout帮我们实现测量的工作,我们实现onlayout()即可
onFinishInflate() 当布局加载完成的时候的回调,自定义View的时候我们可以在该方法中获取View的宽高
onSizeChange() 当view的大小发生变化的时候的回调
requestLayout() 重新布局,包括测量measure和布局onlayout
resolveSize(int size, int measureSpec) 算出来的size和测量出来的spec那个合适用那个
public static int resolveSize(int size, int measureSpec) { return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK; } public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }4、setMeasuredDimension
5、MeasureSpec
一个MeasureSpec封装了从父容器传递给子容器的布局要求,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec
这是一个含mode和size的结合体,不需要我们来具体的关心。当在测量时,可以调用MeasureSpec.getSize|getMode 得到相应的size和mode。然后使用MeasureSpec.makeMeasureSpec(size,mode); 来创建MeasureSpec对象。那么mode是怎么来的呢?是根据使用该自定义view时的layoutWith|height参数决定的,所以不能自己随便new一个。而size可以自己指定,也可以直接使用 measureSpec.getSize。
5.1、常用方法
MeasureSpec.getSize(widthMeasureSpec) 获取view的宽MeasureSpec.getMode(int measurespec) 获取测量模式MeasureSpec.makeMeasureSpec(size,mode) 组装32位的测量策略,高2位:mode,低30位:size5.2、测量策略
MeasureSpec.AT_MOST 表示子布局被限制在一个最大值内,一般当childView设置其宽、高为wrap_content时,ViewGroup会将其设置为AT_MOST
MeasureSpec.EXACTLY 表示设置了精确的值,一般当childView设置其宽、高为精确值、match_parent时,ViewGroup会将其设置为EXACTLY
MeasureSpec.UNSPECIFIED 表示子布局想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见
5.3、获取View的宽高
getMeasuredHeight(),测量后的高度,实际高度。获取测量完的高度,只要在onMeasure方法执行完,就可以用它获取到宽高,在自定义控件内部多使用这个使用view.measure(0,0)方法可以主动通知系统去测量,然后就可以直接使用它获取宽高
getHeight(),显示的高度。必须在onLayout方法执行完后,才能获得宽高
5.4、measure(0,0)
view.measure(0,0)主动通知系统去测量
View view = new View(context);view.measure(0,0);//等价于下面的代码// MeasureSpec.UNSPECIFIED = 0int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);view.measure(widthMeasureSpec,heightMeasureSpec);int measureWidth = view.getMeasuredWidth();view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int width = view.getWidth(); }});5.5、getMeasuredWidth()与getWidth()的区别
首先getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。
getWidth():只有调用了onLayout()方法,getWidth()才赋值,显示的宽度
getMeasureWidth():获取测量完的宽度,只要在onMeasure()方法执行完,就可以用它获取到高度,实际的宽度
6、getChildMeasureSpec
getChildMeasureSpec( )的总体思路就是通过其父视图提供的MeasureSpec参数得到specMode和specSize,并根据计算出来的specMode以及子视图的childDimension(layout_width和layout_height中定义的)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension,而该函数的参数正是这里计算出来的

/** * Does the hard part of measureChildren: figuring out the MeasureSpec to * pass to a particular child. This method figures out the right MeasureSpec * for one dimension (height or width) of one child view. * * The goal is to combine information from our MeasureSpec with the * LayoutParams of the child to get the best possible results. For example, * if the this view knows its size (because its MeasureSpec has a mode of * EXACTLY), and the child has indicated in its LayoutParams that it wants * to be the same size as the parent, the parent should ask the child to * layout given an exact size. * * @param spec The requirements for this view * @param padding The padding of this view for the current dimension and * margins, if applicable * @param childDimension How big the child wants to be in the current * dimension * @return a MeasureSpec integer for the child */ public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); 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't 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't 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; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }7、measureChild
/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding. * The heavy lifting is done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param parentHeightMeasureSpec The height requirements for this view */ 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); }8、measureChildren
/** * Ask all of the children of this view to measure themselves, taking into * account both the MeasureSpec requirements for this view and its padding. * We skip children that are in the GONE state The heavy lifting is done in * getChildMeasureSpec. * * @param widthMeasureSpec The width requirements for this view * @param heightMeasureSpec The height requirements for this view */ protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }9、measureChildWithMargins
/** * Ask one of the children of this view to measure itself, taking into * account both the MeasureSpec requirements for this view and its padding * and margins. The child must have MarginLayoutParams The heavy lifting is * done in getChildMeasureSpec. * * @param child The child to measure * @param parentWidthMeasureSpec The width requirements for this view * @param widthUsed Extra space that has been used up by the parent * horizontally (possibly by other children of the parent) * @param parentHeightMeasureSpec The height requirements for this view * @param heightUsed Extra space that has been used up by the parent * vertically (possibly by other children of the parent) */ 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); }二、layout 布局
View的layout()
public final void layout(int l, int t, int r, int b) { ..... //设置View位于父视图的坐标轴 boolean changed = setFrame(l, t, r, b); //判断View的位置是否发生过变化,看有必要进行重新layout吗 if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } //调用onLayout(changed, l, t, r, b); 函数 onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; } mPrivateFlags &= ~FORCE_LAYOUT; .....}onLayout()
setFrame(l, t, r, b) 设置View位于父视图的坐标轴
int childCount = getChildCount() ;for(int i=0 ;i<childCount ;i++){ View child = getChildAt(i) ; //整个layout()过程就是个递归过程 child.layout(l, t, r, b) ;}public final int getMeasuredWidth() { return mMeasuredWidth & MEASURED_SIZE_MASK;}public final int getWidth() { return mRight - mLeft;}View中:
public void layout(int l,int t,int r,int b) { ... onLayout ...}//changed 表示是否有新的位置或尺寸protected void onLayout(boolean changed,int left,int top,int right,int bottom) { //空实现}ViewGroup中:
public final void layout(int l,int t,int r,int b) { ... super.layout(l, t, r, b); ...}//changed 表示是否有新的位置或尺寸protected abstract void onLayout(boolean changed, int l,int t, int r,int b);说明:
自定义一个view时,建议重写onLayout,以设定它的位置。 在外部调用时,调用layout(),触发设定位置。
自定义一个viewGroup时,必须且只能重写onLayout。 需要在设定子view的位置:调用subview.layout(); 触发
三、draw 绘制
Step 1, draw the background, if needed 绘制背景Step 2, save the canvas’ layersStep 3, draw the content 绘制内容Step 4, draw the children 绘制子viewStep 5, draw the fade effect and restore layersStep 6, draw decorations (scrollbars) 对View的滚动条进行绘制onDraw()
绘制视图自身
dispatchDraw(canvas)
用来绘制子View的,遍历子View然后drawChild(),drawChild()方法实际调用的是子View.draw()方法,ViewGroup类已经为我们实现绘制子View的默认过程,这个实现基本能满足大部分需求,所以ViewGroup类的子类(LinearLayout,FrameLayout)也基本没有去重写dispatchDraw方法
无论是View还是ViewGroup对它们俩的调用顺序都是onDraw()->dispatchDraw()
但在ViewGroup中,当它有背景的时候就会调用onDraw()方法,否则就会跳过onDraw()直接调用dispatchDraw();所以如果要在ViewGroup中绘图时,往往是重写dispatchDraw()方法。dispatchDraw()方法内部遍历子view,调用子view的绘制方法来完成绘制工作
在View中,onDraw()和dispatchDraw()都会被调用的,所以我们无论把绘图代码放在onDraw()或者dispatchDraw()中都是可以得到效果的,但是由于dispatchDraw()的含义是绘制子控件,所以原则来上讲,在绘制View控件时,我们是重新onDraw()函数
在绘制View控件时,需要重写onDraw()函数,在绘制ViewGroup时,需要重写dispatchDraw()函数。
在自定义控件public class CircleProgressView extends LinearLayout的时候,如果不设置背景的话setBackground()的话,是不会走onDraw()方法的 dispatchDraw()绘制具体的内容(图片和文本)
View中:
public void draw(Canvas canvas) {/*1. Draw the background 绘制背景2. If necessary, save the canvas' layers to prepare for fading 如有必要,颜色渐变淡之前保存画布层(即锁定原有的画布内容)3. Draw view's content 绘制view的内容4. Draw children 绘制子view5. If necessary, draw the fading edges and restore layers 如有必要,绘制颜色渐变淡的边框,并恢复画布(即画布改变的内容附加到原有内容上)6. Draw decorations (scrollbars for instance) 绘制装饰,比如滚动条*/ ... if (!dirtyOpaque) { drawBackground(canvas); //背景绘制 } // skip step 2 & 5 if possible (common case) 通常情况跳过第2和第5步 ... if (!dirtyOpaque) onDraw(canvas); //调用onDraw dispatchDraw(canvas); //绘制子view onDrawScrollBars(canvas); //绘制滚动条 ...}protected void dispatchDraw(Canvas canvas) { //空实现 }protected void onDraw(Canvas canvas) { //空实现 }ViewGroup中:
protected void dispatchDraw(Canvas canvas) { ... drawChild(...); //绘制子view ...}protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime);}说明:
自定义一个view时,重写onDraw。 调用view.invalidate(),会触发onDraw和computeScroll()。前提是该view被附加在当前窗口上view.postInvalidate(); //是在非UI线程上调用的
自定义一个ViewGroup,重写onDraw。 onDraw可能不会被调用,原因是需要先设置一个背景(颜色或图)。表示这个group有东西需要绘制了,才会触发draw,之后是onDraw。因此,一般直接重写dispatchDraw来绘制viewGroup
自定义一个ViewGroup,dispatchDraw会调用drawChild。