本文主要是介绍9.view 作图过程,讲讲draw/onDraw和drawChild,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
9.view 绘制过程,讲讲draw/onDraw和drawChild 转载请标明出处:
http://blog.csdn.net/yujun411522/article/details/46226135
本文出自:【yujun411522的博客】
9.1 view的绘制过程
view的绘制过程在UI中还是非常重要的,view的绘制是从根节点,自上而下的一个过程,主要经历三个过程:
先看performTraversals函数代码,这段代码很长,主要工作可以归结为三个函数:
ViewRootImpl.java中performTraversals()private void perfromTraversals(){final View host = mView;....//1.measure过程host.measure(childWidthMeasureSpec,childHeightMeasureSpec);....//2.layout过程host.layout(0,0,host.getMeasureWidth(),host.getMeasureHeight); .....//3 draw过程draw(fullRedrawNeeded);}
下面分别来介绍着三个过程:
9.1.1 measure
measure中主要涉及以下几个方法:
public final void measure(int widthMeasureSpec,int heightMeasureSpec)
protected voidonMeasure(int widthMeasureSpec,int heightMeasureSpec)
protected final void setMeasureDimension(int measuredWidht,int measureHeight)
这里先还要介绍一个类:MeasureSpec。引入这个类的主要目的是在android中子view的尺寸还要受到父view的限制。
一个MeasureSpec是一个int 类型,共32b,有两部分组成:高两位是mode,剩下的30位是size
mode中有三种模式:
1 UNSPECIFIED:不受到父view的限制,可以设置为任意值。基本上不用到。
2 EXACTLY:父view中指定子view的大小,不管子view如果设置。比如:match_parent、或者具体的大小
3 AT_MOST:父view中指定子view最多只能这么多。比如:wrap_content
在ViewRootImpl中已经说明了对应关系
private int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT</strong>:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT</strong>:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;}
可以看出match_parent和具体尺寸对应的都是EXACTLY,wrap_content对应的是AT_MOST。
接下来看一下view.measure函数的处理过程
public final void measure (int widthMeasureSpec, int heightMeasureSpec) {....// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);.... mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;}
调用了onMeasure方法:
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimensiongetDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
里面涉及了三个方法: getSuggestedMinimumWidth,getDefaultSize,setMeasuredDimension
先看view.getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {int suggestedMinWidth = mMinWidth ;if (mBGDrawable != null) {final int bgMinWidth = mBGDrawable.getMinimumWidth();if (suggestedMinWidth < bgMinWidth) {suggestedMinWidth = bgMinWidth;}}return suggestedMinWidth</strong>;//返回背景图最小值和view最小值中的较大者}
getSuggestedMinimumHeight()方法类似,不做介绍。
再看view.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;//UNSPECIFIED,返回值为size值break;case MeasureSpec.AT_MOST://AT_MOST,返回值为size值case MeasureSpec.EXACTLY:result = specSize;//EXACTLY,返回值为specSize值break;}return result;}
再看setMeasuredDimension方法:
protected final void setMeasuredDimension (int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= MEASURED_DIMENSION_SET ;}
我们在override完onMeasure方法之后一定要执行这个setMeasuredDimension方法,这个方法的目的就是保存measure之后的值。
以上介绍的是view的measure过程,如果是viewGroup时流程会有区别:viewGroup循环measure所有子view。先看一看ViewGroup中的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) {//过滤掉GONE的viewmeasureChild(child, widthMeasureSpec, heightMeasureSpec);//让所有的子view measure自己。调用measureChild方法。}}}
调用ViewGroup.measureChild(view,int,int)。类似measureChild函数还有measureChildWithMargins(view,int,int))
/*** 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();//主要工作还是在getChildMeasureSpec函数中进行final int childWidthMeasureSpec = getChildMeasureSpec</strong>(parentWidthMeasureSpec,mPaddingLeft+mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec</strong>(parentHeightMeasureSpec, mPaddingTop+ mPaddingBottom, lp.height);child.measure</strong>(childWidthMeasureSpchildHeightMeasureSpec ec, childHeightMeasureSpec);//计算完childWidthMeasureSpec 、childHeightMeasureSpec 调用子view的measure方法}
其中调用了ViewGroup.getChildMeasureSpec方法,这个方法比较复杂: 此方法的目的是根据父view对子view的MeasureSpec和子view的layout参数产生一个最合适的MeasureSpec
/ * 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 ourMeasureSpec 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 LayoutParamsthat 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 uscase 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 uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = 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 becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
可以看出上面的处理过程还是挺复杂的,该方法返回父view对子view的一个32b的measureSpec。 measureChild方法最后调用view的measure方法,再按照view的measure流程绘制。
具体的可以参看不同view控件,以及measure的实现。
9.1.2 layout
measure的过程就是确定view的大小,而layout的过程则是确定view的位置。
看一下view.layout()方法:
public void layout(int l, int t, int r, int b) {int oldL = mLeft ;int oldT = mTop ;int oldB = mBottom ;int oldR = mRight ;boolean changed = setFrame(l, t, r, b);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方法mPrivateFlags &= ~LAYOUT_REQUIRED ;if (mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>) 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);//通知对象的listener调用onLayoutChange}}}mPrivateFlags &= ~FORCE_LAYOUT ;}
调用onLayout方法,再看onLayout方法是一个空实现:
protected void onLayout( boolean changed, int left, int top, int right, int bottom) { }
也就是我们需要自己实现onLayout方法。
这是view的layout过程,再看viewGroup过程,先看viewGroup.layout方法:
@Overridepublic final void layout(int l, int t, int r, int b) {if (mTransition == null || !mTransition.isChangingLayout()) {super.layout</strong>(l, t, r, b);//调用父类view的layout方法</strong>} else {// record the fact that we noop'd it; request layout when transition finishesmLayoutSuppressed = true;}}
调用父类view的layout方法,再看onLayout方法
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
viewGroup.onLayout是抽象函数。
ViewGroup对应的就是一个个具体的布局,看具体布局是如何实现的。
LinearLayout的layoutVertical方法
void layoutVertical() {// .... for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {childTop += lp. topMargin ;setChildFrame</strong>(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}}
其中出现了LinearLayout.setChildFrame方法:
private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height);}
再看FrameLayout的onLayout方法:
protected void onLayout( boolean changed, int left, int top, int right, int bottom) {final int count = getChildCount(); for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight(); ..switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK ) {..}switch (verticalGravity) {..}child.layout</strong>(childLeft, childTop, childLeft + width, childTop + height);}}}
可以看出都是调用子view中的layout方法。所以在自定义view的时候需要自己重写view的onLayout方法。
9.1.3 draw
得到位置之后,最后就是绘制view了。
view做的事情主要有以下几个部分:
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)
比较重要的是step3和step4:
// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);其中onDraw实现:空实现。因为view怎样展示是由具体的view自行实现。protected void onDraw(Canvas canvas) {}// Step 4, draw the childrendispatchDraw(canvas);
里面涉及了两个函数,onDraw和dispatchDraw函数,先看onDraw函数:
protected void onDraw(Canvas canvas) { }//空实现,因为view到底怎么展示是view自己决定的,所以要空实现
再看dispatchDraw方法:
protected void dispatchDraw(Canvas canvas) { }也是空实现,因为view中没有子view,也就不需要向子view派发draw请求了,所以是空实现。
viewGroup中没有重写draw函数,所以是继承view的draw函数;
viewGroup中也没有重写onDraw函数,所以是继承view的onDraw函数,也就是空实现
再看viewGroup中的viewGroup.dispatchDraw(Canvas)方法:
protected void dispatchDraw(Canvas canvas) {final int count = mChildrenCount;final View[] children = mChildren ;int flags = mGroupFlags ; .. if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {for (int i = 0; i < count; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |= drawChild</strong>(canvas, child, drawingTime);}}} else {for (int i = 0; i < count; i++) {final View child = children[getChildDrawingOrder(count, i)];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |=drawChild</strong>(canvas, child, drawingTime);}}}// Draw any disappearing views that have animationsif (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren ;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finishfor (int i = disappearingCount; i >= 0; i--) {final View child = disappearingChildren.get(i);more |= drawChild</strong>(canvas, child, drawingTime);}}..}
又调用了drawChild(Canvas,View,Long)方法,此方法主要作用是draw viewGroup中的某一个view,代码太长就不贴出现了。
这篇关于9.view 作图过程,讲讲draw/onDraw和drawChild的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!