Android 中View的绘制机制源码分析 一

尊重原创: http://blog.csdn.NET/yuanzeyao/article/details/46765113

差不多半年没有写博客了,一是因为工作比较忙,二是觉得没有什么内容值得写,三是因为自己越来越懒了吧,不过最近我对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家。在之后的几篇博客中,我会给大家分享如下的内容:

1、View中measure(),layout(),draw()函数执行过程分析,带领大家详细分析View的尺寸测量过程,位置计算,并最终绘制到UI上的过程

2、以LinearLayout为例讲解ViewGroup尺寸计算,位置计算,以及绘制过程

3、更深层次的理解LayoutParams的意义

4、LayoutInflater创建View的过程分析,详细分析inflate(int resource, ViewGroup root, boolean attachToRoot)方法中各个参数的意义

掌握上面几个知识点对于自定义View有非常重要的意义的,而且据我所知自定义View在面试过程中是必问知识点。

以上内容都是Android中View系统比较重要的一些内容,View系统的功能主要包括用户输入消息到消息处理的整个过程,以及UI的绘制,用户输入消息以及消息处理的部分我之前也有写过几篇文章,如果读者用兴趣可以去了解下:

Android 系统Touch事件传递机制 上:http://blog.csdn.Net/yuanzeyao/article/details/37961997

Android 系统Touch事件传递机制 下:http://blog.csdn.net/yuanzeyao/article/details/38025165

Android 系统Key事件传递机制 上:http://blog.csdn.net/yuanzeyao/article/details/13630909

Android 系统Key事件传递机制 下:http://blog.csdn.net/yuanzeyao/article/details/13631139

 

由于涉及的内容比较多,所以我打算使用 多篇文章来讲解上述内容,敬请期待。

那么现在就开始学习View的measure过程吧,measure过程主要作用就是计算一个View的大小,这个其实很好理解,因为任何一个View在绘制到UI上时,必须事先知道这个View的大小,不然是无法绘制的。

平时我们在指定一个view的大小时,通常就是在xml文件中设置layout_width和layout_hegiht属性,这里我要提出一个问题:为什么View的宽度和高度对应的属性名前面有layout而不是直接叫width和height?先记住这个问题吧,等你看完本文的内容相信你就明白了。其实measuer过程就将layout_width和layout_height这些属性变为具体的数字大小。

 

当我们想要将一个xml文件显示到UI上时,通常就是将该xml文件的id传入到Activity的setContentView中去,其实最终就会调用到ViewRoot的performTraversals方法,此方法承担了Android的View的绘制工作,这个方法代码非常多,但是逻辑非常简单,主要包含了三个阶段:

第一个阶段就是我们今天要学习的measure,第二个阶段就是layout,第三个阶段就是draw,measure阶段就是得到每个View的大小,layout阶段就是计算每个View在UI上的坐标,draw阶段就是根据前面两个阶段的数据进行UI绘制。

 

首先我们看看ViewRoot的performTraversals方法的部分代码(使用的2.3代码,选择2.3代码的原因是因为2.3的版本逻辑比4.x版本简单,而且主要逻辑还是一样的)

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    private void performTraversals() {  
  2.         // Section one mView就是DecorView,  
  3.         final View host = mView;  
  4.   
  5.   
  6.   
  7.         //Section two  
  8.         int desiredWindowWidth;  
  9.         int desiredWindowHeight;  
  10.         int childWidthMeasureSpec;  
  11.         int childHeightMeasureSpec;  
  12.   
  13.         ...  
  14.   
  15.   
  16.         Rect frame = mWinFrame;  
  17.         if (mFirst) {  
  18.             fullRedrawNeeded = true;  
  19.             mLayoutRequested = true;  
  20.   
  21.             DisplayMetrics packageMetrics =  
  22.                 mView.getContext().getResources().getDisplayMetrics();  
  23.             //Section three  
  24.             desiredWindowWidth = packageMetrics.widthPixels;  
  25.             desiredWindowHeight = packageMetrics.heightPixels;  
  26.   
  27.             // For the very first time, tell the view hierarchy that it  
  28.             // is attached to the window.  Note that at this point the surface  
  29.             // object is not initialized to its backing store, but soon it  
  30.             // will be (assuming the window is visible).  
  31.            ...  
  32.   
  33.         } else {  
  34.             //Section four  
  35.             desiredWindowWidth = frame.width();  
  36.             desiredWindowHeight = frame.height();  
  37.             if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {  
  38.                 if (DEBUG_ORIENTATION) Log.v("ViewRoot",  
  39.                         "View " + host + " resized to: " + frame);  
  40.                 fullRedrawNeeded = true;  
  41.                 mLayoutRequested = true;  
  42.                 windowResizesToFitContent = true;  
  43.             }  
  44.         }  
  45.   
  46.      
  47.   
  48.         boolean insetsChanged = false;  
  49.   
  50.         if (mLayoutRequested) {  
  51.             // Execute enqueued actions on every layout in case a view that was detached  
  52.             // enqueued an action after being detached  
  53.             getRunQueue().executeActions(attachInfo.mHandler);  
  54.   
  55.   
  56.             ...  
  57.             //Section five  
  58.             childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);  
  59.             childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);  
  60.   
  61.             // Ask host how big it wants to be  
  62.             if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot",  
  63.                     "Measuring " + host + " in display " + desiredWindowWidth  
  64.                     + "x" + desiredWindowHeight + "...");  
  65.             //Section six  
  66.             host.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  67.   
  68.             if (DBG) {  
  69.                 System.out.println("======================================");  
  70.                 System.out.println("performTraversals -- after measure");  
  71.                 host.debug();  
  72.             }  
  73.         }  
  74.   
  75.         ....  
  76.     }</span>  

 

上面的代码就是第一阶段的主要代码,请看代码中的Section one部分,这里定义了一个View 类型的变量host,它被赋值mView,这里我想说的仅仅是mView就是一个界面的DecorView,如果你还不熟悉DecorView可以看看我的另外一篇文章:

《窗口的创建过程》,Section two分别定义了4个int 类型的变量,前面两个变量在Section three部分或者Section four部分赋值,通常第一次进来是在Section three里面进行赋值,也就是说desiredWindowWidth和disireWindowHeight分别是手机屏幕的宽和高(当然并不总是这样的,这里我们只用考虑简单的一种情况),在Section five部分分别对childWidthMeasureSpec和childHeightMeasureSpec进行赋值,这里调用了一个getRootMeasureSpec的方法,我们后面再分析它。在Setion six部分调用host.measure来计算View的大小,到这里performTraversals中mersure的调用过程就算结束了,但是getRootMeasureSpec和host的measure方法我们还不清楚它们到底做了什么,下面就来分析这两个方法吧:

先看看getRootMeasureSpec方法吧。

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    private int getRootMeasureSpec(int windowSize, int rootDimension) {  
  2.         int measureSpec;  
  3.         switch (rootDimension) {  
  4.   
  5.         case ViewGroup.LayoutParams.MATCH_PARENT:  
  6.             // Window can't resize. Force root view to be windowSize.  
  7.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
  8.             break;  
  9.         case ViewGroup.LayoutParams.WRAP_CONTENT:  
  10.             // Window can resize. Set max size for root view.  
  11.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
  12.             break;  
  13.         default:  
  14.             // Window wants to be an exact size. Force root view to be that size.  
  15.             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
  16.             break;  
  17.         }  
  18.         return measureSpec;  
  19.     }</span>  

看了实现之后,你是不是觉得这个方法实现超简单,以getRootMeasureSpec(desiredWindowWidth,lp.width)为例,我们知道第一个参数就是屏幕的宽度,第二个参数是一个View的LayoutParams中的width属性,其实这个参数是在Activity的

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;"> void makeVisible() {  
  2.         if (!mWindowAdded) {  
  3.             ViewManager wm = getWindowManager();  
  4.             wm.addView(mDecor, getWindow().getAttributes());  
  5.             mWindowAdded = true;  
  6.         }  
  7.         mDecor.setVisibility(View.VISIBLE);  
  8.     }</span>  


makeVisible方法传入的,makeVisible是在Activity的onResume里面调用,我们先不关心这个,我们关心的是这个lp是怎么创建的,我们看看getWindow.getAttributes()做了什么吧

 

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">  // The current window attributes.  
  2.     private final WindowManager.LayoutParams mWindowAttributes =  
  3.         new WindowManager.LayoutParams();</span>  


通过源码,找到Window的getAttributes方法,该方法返回mWindowAttributes值,我们看看WindowManager.LayoutParams这个类的空构造函数吧

 

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">     
  2.         public LayoutParams() {  
  3.             super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);  
  4.             type = TYPE_APPLICATION;  
  5.             format = PixelFormat.OPAQUE;  
  6.         }</span>  


看了构造函数后,我们发现layout_width和laout_height都是MATCH_PARENT。关于lp这个参数我们先看到这里,我们继续看getRootMeasureSpec这个方法,

 

这里出现了一个MeasureSpec的陌生类,先看看MeasureSpec是何方圣神。MeasureSpec是定义在View中的一个内部类,这个类里面有几个比较重要的常量:

 

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">       private static final int MODE_SHIFT = 30;  
  2.         private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
  3.   
  4.         /** 
  5.          * Measure specification mode: The parent has not imposed any constraint 
  6.          * on the child. It can be whatever size it wants. 
  7.          */  
  8.         public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
  9.   
  10.         /** 
  11.          * Measure specification mode: The parent has determined an exact size 
  12.          * for the child. The child is going to be given those bounds regardless 
  13.          * of how big it wants to be. 
  14.          */  
  15.         public static final int EXACTLY     = 1 << MODE_SHIFT;  
  16.   
  17.         /** 
  18.          * Measure specification mode: The child can be as large as it wants up 
  19.          * to the specified size. 
  20.          */  
  21.         public static final int AT_MOST     = 2 << MODE_SHIFT;</span>  

 

我们知道Java中的int类型占用32位,随意这几个变量在内存中的表现形式如下:

MODE_MASK:  11000000 00000000 00000000 00000000

UNSPECIFIED: 000000000 00000000 00000000 00000000

EXACTLY:         01000000 00000000 00000000 00000000

AT_MOST:       10000000 00000000 00000000 00000000

也就是说每个高2位表示的model,第30位才真正表示尺寸的大小

 

有了上面的基础之后,相信理解下面三个方法就不难了

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">/** 
  2.          * Creates a measure specification based on the supplied size and mode. 
  3.          * 
  4.          * The mode must always be one of the following: 
  5.          * <ul> 
  6.          *  <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li> 
  7.          *  <li>{@link android.view.View.MeasureSpec#EXACTLY}</li> 
  8.          *  <li>{@link android.view.View.MeasureSpec#AT_MOST}</li> 
  9.          * </ul> 
  10.          * 
  11.          * @param size the size of the measure specification 
  12.          * @param mode the mode of the measure specification 
  13.          * @return the measure specification based on size and mode 
  14.          */  
  15.         public static int makeMeasureSpec(int size, int mode) {  
  16.             return size + mode;  
  17.         }  
  18.   
  19.         /** 
  20.          * Extracts the mode from the supplied measure specification. 
  21.          * 
  22.          * @param measureSpec the measure specification to extract the mode from 
  23.          * @return {@link android.view.View.MeasureSpec#UNSPECIFIED}, 
  24.          *         {@link android.view.View.MeasureSpec#AT_MOST} or 
  25.          *         {@link android.view.View.MeasureSpec#EXACTLY} 
  26.          */  
  27.         public static int getMode(int measureSpec) {  
  28.             return (measureSpec & MODE_MASK);  
  29.         }  
  30.   
  31.         /** 
  32.          * Extracts the size from the supplied measure specification. 
  33.          * 
  34.          * @param measureSpec the measure specification to extract the size from 
  35.          * @return the size in pixels defined in the supplied measure specification 
  36.          */  
  37.         public static int getSize(int measureSpec) {  
  38.             return (measureSpec & ~MODE_MASK);  
  39.         }</span>  


第一个方法makeMeasureSpec就是讲size和mode相加返回其结果,第二个getMode就是获取高2位的值,getSize就是获取低30位的值

 

 

看明白了这里,我们就回到getRootMeasureSpec吧,我们知道lp.width属性通常有三种:match_parent(fill_parent),wrap_content,具体一个大小(如100dip),而这里通过我们上面的分析,知道宽和高均是match_parent。通过代码我们知道这三种情况对应的mode分别是:

EXACTLY,AT_MOST,EXACTLY,也就是说math_parent和具体的大小(100dip)对应的都是EXACTLY。最后根据得到的mode和屏幕的宽度调用makeMeasureSpec方法得到一个int类型的值赋值给childWidthMeasureSpec,同理得到了childHeightMeasureSpec,并将这两个值传入measure中。下面我们就看看measure做了什么

 

由于这里调用的是host的measure,而host其实是一个FrameLayout,所以我不打算继续使用这个例子将View的测量过程了,但是ViewGroup是没有改写measure的,所以其实调用的还是View的measure方法,measure方法的源码如下:

 

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.                 widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.                 heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.   
  6.             // first clears the measured dimension flag  
  7.             mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  8.   
  9.             if (ViewDebug.TRACE_HIERARCHY) {  
  10.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  11.             }  
  12.   
  13.             // measure ourselves, this should set the measured dimension flag back  
  14.             onMeasure(widthMeasureSpec, heightMeasureSpec);  
  15.   
  16.             // flag not set, setMeasuredDimension() was not invoked, we raise  
  17.             // an exception to warn the developer  
  18.             if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  19.                 throw new IllegalStateException("onMeasure() did not set the"  
  20.                         + " measured dimension by calling"  
  21.                         + " setMeasuredDimension()");  
  22.             }  
  23.   
  24.             mPrivateFlags |= LAYOUT_REQUIRED;  
  25.         }  
  26.   
  27.         mOldWidthMeasureSpec = widthMeasureSpec;  
  28.         mOldHeightMeasureSpec = heightMeasureSpec;  
  29.     }</span>  

 

我们看到measure方法其实是final的,所以ViewGroup是无法改写此方法的。通常一个具体的ViewGroup都是改写onMeasure方法,这点你可以去看看LinearLayout和FrameLayout,他们在onMeasure方法里面都间接调用了ViewGroup的measureChildWithMargins方法,今天我们就以measureChildWithMargins这个方法为入口分析View的测量过程。measureChildWithMargins方法的源码如下:

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    protected void measureChildWithMargins(View child,  
  2.             int parentWidthMeasureSpec, int widthUsed,  
  3.             int parentHeightMeasureSpec, int heightUsed) {  
  4.         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();  
  5.   
  6.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,  
  7.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  
  8.                         + widthUsed, lp.width);  
  9.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,  
  10.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin  
  11.                         + heightUsed, lp.height);  
  12.   
  13.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  
  14.     }</span>  

这里我们简化下情况,我们假设ViewGroup里面所有的孩子都是View,没有ViewGroup。

 

下面我们分三步来分析measureChildWithMargins方法:

1、获取孩子的LayoutParams

2、调用getChildMeasureSpec方法得到孩子的measureSpec(包括widthSpec和heightSpec)

我们看看getChildMeasureSpec做了什么,先看看它的几个参数,以获取孩子的widthSpec为例 ,第一个参数是ViewGroup的widthSpec,第二个参数是ViewGroup已经被使用的width,第三个是lp.width,接下来看看源码:

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">   */  
  2.     public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  3.         int specMode = MeasureSpec.getMode(spec);  
  4.         int specSize = MeasureSpec.getSize(spec);  
  5.   
  6.         int size = Math.max(0, specSize - padding);  
  7.   
  8.         int resultSize = 0;  
  9.         int resultMode = 0;  
  10.   
  11.         switch (specMode) {  
  12.         // Parent has imposed an exact size on us  
  13.         case MeasureSpec.EXACTLY:  
  14.             if (childDimension >= 0) {  
  15.                 resultSize = childDimension;  
  16.                 resultMode = MeasureSpec.EXACTLY;  
  17.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  18.                 // Child wants to be our size. So be it.  
  19.                 resultSize = size;  
  20.                 resultMode = MeasureSpec.EXACTLY;  
  21.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  22.                 // Child wants to determine its own size. It can't be  
  23.                 // bigger than us.  
  24.                 resultSize = size;  
  25.                 resultMode = MeasureSpec.AT_MOST;  
  26.             }  
  27.             break;  
  28.   
  29.         // Parent has imposed a maximum size on us  
  30.         case MeasureSpec.AT_MOST:  
  31.             if (childDimension >= 0) {  
  32.                 // Child wants a specific size... so be it  
  33.                 resultSize = childDimension;  
  34.                 resultMode = MeasureSpec.EXACTLY;  
  35.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  36.                 // Child wants to be our size, but our size is not fixed.  
  37.                 // Constrain child to not be bigger than us.  
  38.                 resultSize = size;  
  39.                 resultMode = MeasureSpec.AT_MOST;  
  40.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  41.                 // Child wants to determine its own size. It can't be  
  42.                 // bigger than us.  
  43.                 resultSize = size;  
  44.                 resultMode = MeasureSpec.AT_MOST;  
  45.             }  
  46.             break;  
  47.   
  48.         // Parent asked to see how big we want to be  
  49.         case MeasureSpec.UNSPECIFIED:  
  50.             if (childDimension >= 0) {  
  51.                 // Child wants a specific size... let him have it  
  52.                 resultSize = childDimension;  
  53.                 resultMode = MeasureSpec.EXACTLY;  
  54.             } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  55.                 // Child wants to be our size... find out how big it should  
  56.                 // be  
  57.                 resultSize = 0;  
  58.                 resultMode = MeasureSpec.UNSPECIFIED;  
  59.             } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  60.                 // Child wants to determine its own size.... find out how  
  61.                 // big it should be  
  62.                 resultSize = 0;  
  63.                 resultMode = MeasureSpec.UNSPECIFIED;  
  64.             }  
  65.             break;  
  66.         }  
  67.         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  68.     }</span>  


相信有了前面的基础,看这段代码应该很容易,其实就是根据ViewGroup的mode和size以及lp.width的值来创建View的measureSpec。现在知道我前面提的问题的答案了吗,为什么width前面要加一个layout,因为子View的大小时自己(子View)和ViewGroup(父View)共同决定的。

 

 

回到measureChildWithMargins 看第三步:调用了child.measure。并且参数就是第二步中得到的,另外注意这个child就是一个普通的View(因为我们已经假设ViewGroup里面没有ViewGroup,只有View)

 

 

由于是View调用measure,所以measure中调用onMeasure也是View中的,我们看看View的onMeasuere方法吧

 

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4.     }</span>  

 

这里出现了一个重要的方法getDefaultSize,其代码如下:

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">    public static int getDefaultSize(int size, int measureSpec) {  
  2.         int result = size;  
  3.         int specMode = MeasureSpec.getMode(measureSpec);  
  4.         int specSize =  MeasureSpec.getSize(measureSpec);  
  5.   
  6.         switch (specMode) {  
  7.         case MeasureSpec.UNSPECIFIED:  
  8.             result = size;  
  9.             break;  
  10.         case MeasureSpec.AT_MOST:  
  11.         case MeasureSpec.EXACTLY:  
  12.             result = specSize;  
  13.             break;  
  14.         }  
  15.         return result;  
  16.     }</span>  


该方法根据measureSpec的mode决定返回值是size还是specSize。在多数情况下载mode是AT_MOST或者EXACTLY,(UNSPECIFIED通常出现在我们为了获得某个view的大小时,调用此view.measure(0,0)的时候出现.),在onMeasure中会调用setMeasuredDimension()方法将得到的大小分别赋值给mMeasuredWidth,mMeasuredHeight,从而View的大小就测量完成了。

 

代码如下:

 

[java] view plain copy
 
 print?
  1. <span style="font-family:Comic Sans MS;font-size:18px;">  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.         mMeasuredWidth = measuredWidth;  
  3.         mMeasuredHeight = measuredHeight;  
  4.   
  5.         mPrivateFlags |= MEASURED_DIMENSION_SET;  
  6.     }</span>  

 

到这里View的测量过程告一段落了,至于ViewGroup的测量过程在下篇文章中使用LinearLayout分析一下吧。

 

Android 中View的绘制机制源码分析 二 已经发布,敬请关注!

 

posted @ 2017-03-12 11:56  天涯海角路  阅读(117)  评论(0)    收藏  举报