View的绘制基本由measure()、layout()、draw()这三个函数完成

函 数 作 用 相 关 方 法
measure() 测量View的宽高 measure(),setMeasuredDimension(),onMeasure()
layout() 计算当前View以及子View的位置 layout(),onLayout(),setFrame()
draw() 视图的绘制 draw(),onDraw()

一、Measure

(一)MeasureSpec的理解

MeasureSpec是View的内部类,它封装了一个View的尺寸和规格。对于View的测量,肯定会和MeasureSpec接触,MeasureSpec是两个单词组成,翻译过来“测量规格”或者“测量参数”,MeasureSpec封装父容器传递给子容器的布局要求,而不是父容器对子容器的布局要求,“传递”两个字很重要,更精确的说法应该这个MeasureSpec是由父View的MeasureSpec和子View的LayoutParams通过简单的计算得出一个针对子View的测量要求,这个测量要求就是MeasureSpec。

个MeasureSpec是大小size和模式mode的组合值,MeasureSpec中的值是一个整型(32位)将size和mode打包成一个Int型,其中前两位是mode,后面30位存的是size。

MeasureSpec一共有三种模式:

模式 作 用
EXACTLY 父View已经为子View设置了尺寸,子View应当服从这些边界,不论子容器想要多大的空间
AT_MOST 子容器可以是声明大小内的任意大小
UPSPECIFIED 父容器对于子容器没有任何限制,子容器想要多大就多大

子View的MeasureSpec是由父View的MeasureSpec和子View的LayoutParams共同决定的。子View的LayoutParams其实就是我们在xml写的时候设置的layout_width和layout_height转化而来的。父View的measure的过程会先测量子View,等子View测量结果出来后,再来测量自己,下面的measureChildWithMargins就是用来测量某个子View的,我们先来看代码来分析是怎样测量的,具体看注释:

measureChildWithMargins

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { //子View的LayoutParams,在xml的layout_width和layout_height,//layout_xxx的值最后都会封装到这个个LayoutParams。final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();   //根据父View的测量规格 && 父View自己的Padding && 子View的Margin && 已经用掉的空间大小widthUsed,//就能算出子View的MeasureSpec,具体计算过程看getChildMeasureSpec方法。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);  //通过父View的MeasureSpec和子View的自己LayoutParams的计算,算出子View的MeasureSpec,//然后父容器传递给子容器,让子View用这个MeasureSpec(一个测量要求,比如不能超过多大)去测量自己,//如果子View是ViewGroup 那还会递归往下测量。child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

getChildMeasureSpec

//spec:表示父View的MeasureSpec;
//padding:父View的Padding+子View的Margin,精确算出子View的MeasureSpec的size;
//childDimension:子View的LayoutParams属性的值(lp.width或者lp.height),
//可以是match_parent、wrap_content、精确值;public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  int specMode = MeasureSpec.getMode(spec);  //获得父View的mode  int specSize = MeasureSpec.getSize(spec);  //获得父View的大小  //子View的大小 = 父View的大小 - 自己的Paddingint size = Math.max(0, specSize - padding);   int resultSize = 0;   //初始化值,最后通过这个两个值生成子View的MeasureSpecint resultMode = 0;   //初始化值,最后通过这个两个值生成子View的MeasureSpecswitch (specMode) {  //1、父View是EXACTLY,即当父View要求一个精确值时,为子View赋值 case MeasureSpec.EXACTLY:   //1.1 子View的width或height是个精确值,如果子view有自己的尺寸,则使用自己的尺寸 if (childDimension >= 0) {            resultSize = childDimension;         //size为精确值  resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  }   //1.2 子View的width或height为MATCH_PARENT/FILL_PARENT,将父View的大小赋值给子View else if (childDimension == LayoutParams.MATCH_PARENT) {  // Child wants to be our size. So be it.  resultSize = size;                   //size为父视图大小  resultMode = MeasureSpec.EXACTLY;    //mode为 EXACTLY 。  }   //1.3 子View的width或height为WRAP_CONTENT,父View的尺寸为子View的最大尺寸  else if (childDimension == LayoutParams.WRAP_CONTENT) {  resultSize = size;                   //size为父视图大小  resultMode = MeasureSpec.AT_MOST;    //mode为AT_MOST 。  }  break;  //2、父View是AT_MOST,即父View给子View了一个最大界限      case MeasureSpec.AT_MOST:  //2.1 子View的width或height是个精确值,如果子view有自己的尺寸,则使用自己的尺寸 if (childDimension >= 0) {  // Child wants a specific size... so be it  resultSize = childDimension;        //size为精确值  resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY 。  }  //2.2 子View的width或height为MATCH_PARENT/FILL_PARENT,父View的尺寸为子View的最大尺寸 else if (childDimension == LayoutParams.MATCH_PARENT) {  resultSize = size;                  //size为父视图大小  resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  }  //2.3 子View的width或height为WRAP_CONTENT,父View的尺寸为子View的最大尺寸else if (childDimension == LayoutParams.WRAP_CONTENT) {  resultSize = size;                  //size为父视图大小  resultMode = MeasureSpec.AT_MOST;   //mode为AT_MOST  }  break;  //3、父View是UNSPECIFIED,即父View对子View没有做任何限制 case MeasureSpec.UNSPECIFIED:  //3.1 子View的width或height是个精确值,如果子view有自己的尺寸,则使用自己的尺寸if (childDimension >= 0) {  resultSize = childDimension;        //size为精确值  resultMode = MeasureSpec.EXACTLY;   //mode为 EXACTLY  }  //3.2 因父布局没有对子View做出限制,当子View为MATCH_PARENT时则大小为0else if (childDimension == LayoutParams.MATCH_PARENT) {  resultSize = 0;                        //size为0! ,其值未定  resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  }   //3.3 因父布局没有对子View做出限制,当子View为WRAP_CONTENT时则大小为0  else if (childDimension == LayoutParams.WRAP_CONTENT) {  resultSize = 0;                        //size为0! ,其值未定  resultMode = MeasureSpec.UNSPECIFIED;  //mode为 UNSPECIFIED  }  break;  }  //根据上面逻辑条件获取的mode和size构建MeasureSpec对象。  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}    

通过源码可以看出:如果在xml中把layout_width或者layout_height把值都写死,那么上述的测量完全就不需要了,之所以要上面的这步测量,是因为match_parent就是充满父容器,wrap_content就是自适应自己多大就多大,我们写代码的时候特别爽,,Google就要帮我们计算你match_parent的时候是多大,wrap_content的是多大,这个计算过程,就是计算出来的父View的MeasureSpec不断往子View传递,结合子View的LayoutParams一起再算出子View的MeasureSpec,然后继续传给子View,不断计算每个View的MeasureSpec,子View有了MeasureSpec才能更测量自己和自己的子View。

1、如果父View的MeasureSpec是EXACTLY

说明父View的大小是确切的,(确切的意思很好理解,如果一个View的MeasureSpec 是EXACTLY,那么它的size 是多大,最后展示到屏幕就一定是那么大)。

(1)如果子View的layout_xxxx是MATCH_PARENT,父View的大小是确切,子View的大小是MATCH_PARENT(充满整个父View),那么子View的大小肯定是确切的,而且大小值就是父View的size。所以子View的size=父View的size,mode=EXACTLY。

(2)如果子View的layout_xxxx是WRAP_CONTENT,也就是子View的大小是根据自己的content来决定的,但是大小不能超过父View的大小。但是子View的是WRAP_CONTENT,我们还不知道具体子View的大小是多少,要等到child.measure(childWidthMeasureSpec,childHeightMeasureSpec)调用的时候才去真正测量子View自己content的大小(比如TextView wrap_content的时候要测量TextView content的大小,也就是字符占用的大小,这个测量就是在 child.measure(childWidthMeasureSpec,childHeightMeasureSpec)的时候,才能测出字符的大小,MeasureSpec的意思就是假设你字符100px,但是MeasureSpec要求最大的只能50px,这时候就要截掉了)。通过上述描述,子View MeasureSpec mode应该是AT_MOST,而size暂定父View的size,最大为父View的大小,不能超过父View的大小(这就是AT_MOST 的意思),然后这个MeasureSpec做为子View的measure方法的参数,做为子View的大小的约束或者说是要求,有了这个MeasureSpec子View再实现自己的测量。

(3)如果如果子View的layout_xxxx是确定的值(200dp),那么就更简单了,不管你父View的mode和size是什么,我都写死了就是200dp,那么控件最后展示就是就是200dp,不管我的父View有多大,也不管我自己的content有多大,反正我就是这么大,所以这种情况MeasureSpec的mode = EXACTLY,大小size=layout_xxxx的那个值。

2、如果父View的MeasureSpec是AT_MOST

说明父View的大小是不确定,最大的大小是MeasureSpec的size值,不能超过这个值。

(1)如果子View的layout_xxxx是MATCH_PARENT,父View的大小是不确定(只知道最大只能多大),子View的大小MATCH_PARENT(充满整个父View),那么子View你即使充满父容器,大小也是不确定的,父View自己都确定不了大小,即使子ViewMATCH_PARENT,但大小肯定也不能确定的,所以子View的mode=AT_MOST,size=父View的size,最大就是父View的大小。

(2)如果子View的layout_xxxx是WRAP_CONTENT,父View的大小是不确定(只知道最大只能多大),子View又是WRAP_CONTENT,那么在子View的content没算出大小之前,子View的大小最大就是父View的大小,所以子View的mode=AT_MOST,而size暂定父View的size,最大就是父View的大小。

(3)如果如果子View 的layout_xxxx是确定的值(200dp),同上,写多少就是多少,改变不了的。

3、如果父View的MeasureSpec是UNSPECIFIED(未指定)

表示没有任何束缚和约束,子View可以得到任意想要的大小,不受约束。不像AT_MOST表示最大只能多大,不也像EXACTLY表示父View确定的大小。

(1)如果子View的layout_xxxx是MATCH_PARENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0。

(2)如果子View的layout_xxxx是WRAP_CONTENT,因为父View的MeasureSpec是UNSPECIFIED,父View自己的大小并没有任何约束和要求,那么对于子View来说无论是WRAP_CONTENT还是MATCH_PARENT,子View也是没有任何束缚的,想多大就多大,没有不能超过多少的要求,一旦没有任何要求和约束,size的值就没有任何意义了,所以一般都直接设置成0。

(3)如果如果子View的layout_xxxx是确定的值(200dp),写多少就是多少,改变不了的(记住,只有设置的确切的值,那么无论怎么测量,大小都是不变的,都是你写的那个值)。

(二)onMeasure()

整个测量过程的入口位于View的measure方法当中,该方法做了一些参数的初始化之后调用了onMeasure方法,测量过程主要是在onMeasure()方法。onMeasure方法的源码如下:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}

很简单这里只有一行代码,涉及到了三个方法:setMeasuredDimension、getDefaultSize、getSuggestedMinimumWidth

getSuggestedMinimumWidth()和getSuggestedMinimumHeight()

//当View没有设置背景时,默认大小就是mMinWidth,这个值对应Android:minWidth属性,如果没有设置时默认为0.
//如果有设置背景,则默认大小为mMinWidth和mBackground.getMinimumWidth()当中的较大值。
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());}

getDefaultSize(int size, int measureSpec)

该方法用来获取View默认的宽高,结合源码来看。

/**
*   有两个参数size和measureSpec
*   1、size表示View的默认大小,它的值是通过`getSuggestedMinimumWidth()方法来获取的
*   2、measureSpec则是我们之前分析的MeasureSpec,里面存储了View的测量值和测量模式
*/
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);//从这里我们看出,对于AT_MOST和EXACTLY在View当中的处理是完全相同的。所以在我们自定义View时要对这两种模式做出处理。switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}

getDefaultSize的第一个参数size等于getSuggestedMinimumXXXX返回的的值(建议的最小宽度和高度),而建议的最小宽度和高度都是由View的Background尺寸与通过设置View的minXXX属性共同决定的,这个size可以理解为View的默认长度,而第二个参数measureSpec,是父View传给自己的MeasureSpec,这个measureSpec是通过测量计算出来的,具体的计算测量过程前面在讲解MeasureSpec已经讲得比较清楚了(是有父View的MeasureSpec和子View自己的LayoutParams共同决定的)只要这个测试的mode不是UNSPECIFIED(未确定的),那么默认的就会用这个测量的数值当做View的高度。

setMeasuredDimension(int measuredWidth, int measuredHeight)

该方法用来设置View的宽高,在我们自定义View时也会经常用到。 View的onMeasure方法默认实现很简单,就是调用setMeasuredDimension(),setMeasuredDimension()可以简单理解就是给mMeasuredWidth和mMeasuredHeight设值,如果这两个值一旦设置了,那么意味着对于这个View的测量结束了,这个View的宽高已经有测量的结果。如果我们想设定某个View的高宽,完全可以直接通过setMeasuredDimension(100,200)来设置死它的高宽(不建议),但是setMeasuredDimension方法必须在onMeasure方法中调用,不然会抛异常。我们来看下对于View来说它的默认高宽是怎么获取的。

在setMeasuredDimension()之后,才可以调用getMeasureWidth()和getMeasuredHeight()来获取视图测量出来的宽高,以此之前调用两个方法得到的都是0。

在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。

对于View默认是测量很简单,大部分情况就是拿计算出来的MeasureSpec的size当做最终测量的大小。而对于其他的一些View的派生类,如TextView、Button、ImageView等,它们的onMeasure方法系统了都做了重写,不会这么简单直接拿 MeasureSpec 的size来当大小,而去会先去测量字符或者图片的高度等,然后拿到View本身content这个高度(字符高度等)。如果MeasureSpec是AT_MOST,而且View本身content的高度不超出MeasureSpec的size,那么可以直接用View本身content的高度(字符高度等),而不是像View.java直接用MeasureSpec的size做为View的大小。

(三)ViewGroup的Measure过程

ViewGroup的测量过程与View有一点点区别,其本身是继承自View,它没有对View的measure方法以及onMeasure方法进行重写。

为什么没有重写onMeasure呢?ViewGroup除了要测量自身宽高外还需要测量各个子View的大小,而不同的布局测量方式也都不同(可参考LinearLayout以及FrameLayout),所以没有办法统一设置。因此它提供了测量子View的方法measureChildren()和measureChild()帮助我们对子View进行测量。大致流程就是遍历所有的子View,然后调用View的measure()方法,让子View测量自身大小。

ViewGroup中定义了一个measureChildren()方法来去测量子视图的大小,如下所示:

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);}}
}

首先去遍历当前布局下的所有子视图,然后逐个调用measureChild()方法来测量相应子视图的大小,如下所示:

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);
}

上面的源码可以看到,分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,然后调用子视图的measure()方法,并把计算出的MeasureSpec传递进去,之后的流程就和前面所介绍的一样了。

当然,onMeasure()方法是可以重写的,也就是说,如果你不想使用系统默认的测量方式,可以按照自己的意愿进行定制。measure过程会因为布局的不同或者需求的不同而呈现不同的形式,使用时还是要根据业务场景来具体分析,如果想再深入研究可以看一下LinearLayout的onMeasure方法。

二、Layout

layout()过程,对于View来说用来计算View的位置参数,对于ViewGroup来说,除了要测量自身位置,还需要测量子View的位置。

mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());

performTraversals方法执行完mView.measure计算出mMeasuredXXX后就开始执行layout函数来确定View具体的位置,我们计算出来的View目前只知道view矩阵的大小,具体这个矩阵放在哪里,这就是layout的工作了。layout的主要作用:根据子视图的大小以及布局参数将View树放到合适的位置上。

(一)layout(l,t,r,b)

通过mView.layout(0, 0,mView.getMeasuredWidth(),mView.getMeasuredHeight());确定视图的位置。既然layout()方法是整个Layout流程的入口,看一下这部分源码:

/**
*  这里的四个参数l、t、r、b分别代表View的左、上、右、下四个边界相对于其父View的距离。
*
*/
public void layout(int l, int t, int r, int b) {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;//这里通过setFrame或setOpticalFrame方法确定View在父容器当中的位置。//即初始化四个顶点的值,然后判断当前View大小和位置是否发生了变化并返回boolean changed = isLayoutModeOptical(mParent) ?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);//如果视图的大小和位置发生变化,会调用onLayout()确定该View所有的子View在父容器的位置//调用onLayout方法。onLayout方法是一个空实现,不同的布局会有不同的实现。if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {onLayout(changed, l, t, r, b);}}

从源码看出,在layout()方法中已经通过:

1、setOpticalFrame(l,t,r,b)或setFrame(l,t,r,b)方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘。setOpticalFrame()内部也是调用了setFrame()。看下setFrame的源码:

protected boolean setFrame(int left, int top, int right, int bottom) {...
// 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
// 即确定了视图的位置mLeft = left;mTop = top;mRight = right;mBottom = bottom;mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
}

2、onLayout(changed, l, t, r, b)方法主要是ViewGroup对子View的位置进行计算。 确定了自身的位置后,就要通过onLayout()确定子View的布局。onLayout()是一个可继承的空方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

如果当前View就是一个单一的View,那么没有子View,就不需要实现该方法。

如果当前View是一个ViewGroup,就需要实现onLayout方法,该方法的实现个自定义ViewGroup时其特性有关,必须自己实现。

在onLayout()过程结束后,我们就可以调用getWidth()方法和getHeight()方法来获取视图的宽高了。

getMeasureWidth()和getWidth()区别:

getMeasureWidth() getWidth()
在measure()过程结束后就可以获取到了 在layout()过程结束后才能获取到
值是通过setMeasuredDimension()方法来进行设置的 值则是通过视图右边的坐标减去左边的坐标计算出来的

(二)总结View的布局流程

三、Draw

draw流程也就是的View绘制到屏幕上的过程,整个流程的入口在View的draw()方法之中,而源码注释也写的很明白,整个过程可以分为6个步骤。除了2和5很少用到之外,通过各个步骤的源码做分析:

public void draw(Canvas canvas) {.../** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      1. 如果需要,绘制背景*      2. 有过有必要,保存当前canvas*      3. 绘制View的内容*      4. 绘制子View*      5. 如果有必要,绘制边缘、阴影等效果*      6. 绘制装饰,如滚动条等*/// Step 1 如果需要,绘制背景...background.draw(canvas);...// skip step 2 & 5 if possible (common case)...// Step 2 有过有必要,保存当前canvas...if (solidColor == 0) {final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;if (drawTop) {canvas.saveLayer(left, top, right, top + length, null, flags);}...// Step 3 绘制View的内容if (!dirtyOpaque) onDraw(canvas);// Step 4 绘制子ViewdispatchDraw(canvas);// Step 5 如果有必要,绘制边缘、阴影等效果if (drawTop) {matrix.setScale(1, fadeHeight * topFadeStrength);matrix.postTranslate(left, top);fade.setLocalMatrix(matrix);canvas.drawRect(left, top, right, top + length, p);}...// Step 6 绘制装饰,如滚动条等onDrawScrollBars(canvas);}

Step1 背景绘制

看注释即可,不是重点

private void drawBackground(Canvas canvas) { Drawable final Drawable background = mBackground; ...... //mRight - mLeft, mBottom - mTop layout确定的四个点来设置背景的绘制区域 if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);   mBackgroundSizeChanged = false; rebuildOutline(); } ...... //调用Drawable的draw() 把背景图片画到画布上background.draw(canvas); ......
}

Step3 对View的内容进行绘制

   /*** 3.绘制View的内容,该方法是一个空的实现,在各个业务当中自行处理。*/protected void onDraw(Canvas canvas) {}

onDraw(canvas)方法是view用来draw自己的,具体如何绘制,颜色线条什么样式就需要子View自己去实现,View.java的onDraw(canvas) 是空实现。

Step4 对当前View的所有子View进行绘制

   /*** 4. 绘制子View。该方法在View当中是一个空的实现,在各个业务当中自行处理。*  在ViewGroup当中对dispatchDraw方法做了实现,主要是遍历子View,并调用子类的draw方法,*  一般我们不需要自己重写该方法。*/protected void dispatchDraw(Canvas canvas) {}

在ViewGroup当中对dispatchDraw方法做了实现,主要是遍历子View,并调用子类的draw方法,一般我们不需要自己重写该方法。

@Overrideprotected void dispatchDraw(Canvas canvas) {...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(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(canvas, child, drawingTime);}}}......}

代码一眼看出,就是遍历子View然后drawChild(),drawChild()方法实际调用的是子View.draw()方法,ViewGroup类已经为我们实现绘制子View的默认过程,这个实现基本能满足大部分需求,所以ViewGroup类的子类(LinearLayout,FrameLayout)也基本没有去重写dispatchDraw方法。

我们在实现自定义控件,除非比较特别,不然一般也不需要去重写它,drawChild()的核心过程就是为子View分配合适的canvas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制了。

Step6 对View的滚动条进行绘制

不是重点,onDrawScrollBars(canvas);

总结View的绘制流程

四、总结

View的绘制流程:OnMeasure()——>OnLayout()——>OnDraw()

各步骤的主要工作:

模式 作 用
onMeasure() 测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure
onLayout() 确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上
onDraw() 绘制视图,ViewRoot创建一个Canvas对象,然后调用OnDraw()。六个步骤:①、绘制背景;②、保存画布(canvas)的图层(Layer);③、绘制View的内容;④、绘制子View,如果没有就不用;⑤、绘制边缘、阴影效果&还原图层(Layer);⑥、绘制滚动条

Android自定义View-View的绘制流程相关推荐

  1. android小球移动代码,Android自定义圆形View实现小球跟随手指移动效果

    本文实例为大家分享了Android实现小球跟随手指移动效果的具体代码,供大家参考,具体内容如下 一. 需求功能 手指在屏幕上滑动,红色的小球始终跟随手指移动. 实现的思路: 1)自定义View,在on ...

  2. html5跟随手指的小球,Android自定义圆形View实现小球跟随手指移动效果(详细介绍)...

    一. 需求功能 手指在屏幕上滑动,红色的小球始终跟随手指移动. 实现的思路: 1)自定义View,在onDraw中画圆作为小球: 2)重写自定义View的onTouchEvent方法,记录触屏坐标,用 ...

  3. android自定义组合view,自定义View之组合View

    前言 自定义View是安卓开发中比较重要的一环,很多地方都需要用到自定义View.而自定义View比较常见的一种形式就是组合View,也是比较简单的一种方式.下面通过一个实例来学习一下自定义组合vie ...

  4. Android自定义一个View实现运动的小人

    实现一个能动的人是我对模仿<奇怪的大冒险>第一阶段的最后一步,这一段时间学会了许多的东西,见到了很多大坑,也顺利脱险了.而本文所说的|能动的人是基于ImageView打造的.在ImageV ...

  5. Android 自定义动画view(小变大,旋转,色值)

    也不知道到看了多少的动画总结了,但是用到的时候太少,过段时间就会忘记了. 既然如此,我选择直接去动手学习,步步进阶. 效果: 上代码之前我们分析一下才会加深自己的印象: 需要画一个矩形 和 一个圆形 ...

  6. 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 )

    文章目录 一. 博客相关资料 及 下载地址 1. 代码查看方法 ( ① 直接获取代码 | ② JAR 包替换 ) 2. 本博客涉及到的源码查看说明 二. Activity 生命周期回调机制 1. An ...

  7. Android源码解析:UI绘制流程之控件绘制

    带着问题看源码 再接再厉,我们来分析UI绘制流程最后一步绘制流程 入口ViewRootImpl.performDraw()方法 private void performDraw() {//...try ...

  8. android自定义组合view,安卓自定义view之组合view

    效果图 image 实现方案 方案概述 通过在xml布局文件中组合控件,通过自定义view类加载xml文件,让外部通过xml属性或者方法来设置数据. 主要实现代码 组合view xml文件 xmlns ...

  9. Android扑克牌抽奖View,android自定义层级view,扑克牌堆叠效果,cascadeLayout

    需要自定义一个组件CascadeLayout,让子view可以像拿扑克牌那样的层叠起来,主要实现效果: 为了设置子view之前的偏移距离,这里需要定义子view相对于上一张卡片的的左边距,上边距.然后 ...

  10. Android源码解析:UI绘制流程之测量.md

    带着问题看源码 书接上文,做安卓开发都知道只要我们在xml布局中填写控件,并设置宽高大小与位置,安卓系统就会将我们想要的布局展示出来,但是这一步是系统是如何做到的呢?这就是上文讲到的UI绘制过程,他一 ...

最新文章

  1. 最完整代码的用php备份mysql数据库
  2. Ansible第一篇:基础
  3. RIA Service 的 SOAP EndPoint
  4. Linux debian/deepin安装apache2(httpd)服务:文件服务器搭建
  5. 8.8-8.10 usaco
  6. Product ID Not in valid range
  7. 收藏!5V转3.3V电平的19种方法技巧
  8. 突然吐字不清_要注意说话吐字不清小心是脑中风前兆
  9. 《Python Cookbook 3rd》笔记(4.16):迭代器代替 while 无限循环
  10. iOS常用的第三方类库
  11. Java 输入输出流 转载
  12. 终于完成了:为什么吾非要亲自搞CDKEY
  13. Kali和Backtrack中更新metasploit后无法连接数据库的问题解决方法
  14. 易语言html截图,易语言如何指定区域截图;易语言怎么才能全屏截图
  15. TOFLE-Mistake
  16. 前端eslint+prettier+lint-staged配置
  17. 现代软件工程讨论第一章-第四章
  18. FreeRTOS原理剖析:空闲任务分析
  19. 金融科技大数据产品推荐:易鑫大数据风控平台
  20. java Serializable

热门文章

  1. 超全的人脸识别数据集汇总
  2. 单片机:楼梯照明灯控制
  3. 多媒体知识,手机电脑设备联用
  4. 成不了AI高手?因为你根本不懂数据!听听这位老教授多年心血练就的最实用统计学...
  5. 搜索中的深度匹配模型
  6. Flash遮罩之放大镜
  7. 数学建模竞赛大汇总,别再被野鸡竞赛坑啦
  8. 细粒度分类网络之WS-DAN论文阅读附代码
  9. 智能优化算法:人工水母搜索算法 -附代码
  10. URI与URL的区别