Android View绘制的三大流程

来源:互联网 发布:义乌淘宝代运营 编辑:程序博客网 时间:2024/05/15 02:18
本文讲的是Android视图的绘制的三大流程,视图的工作流程主要是指测量,布局,绘制这三大流程,即测量,布局和绘制,其中测量确定查看的测量宽高,布局根据测量的宽高确定视图在其父视图中的四个顶点的位置,而平局则将查看绘制到屏幕上,这样通过一个ViewGroup的递归遍历,一个景观树就展现在屏幕上了。说的简单,下面带大家一步一步从源码中分析:

安卓的视图是树形结构的:

基本概念

在介绍视图的三大流程之前,我们必须先介绍一些基本的概念,才能更好地理解这整个过程。

窗口的概念

窗口表示的是一个窗口的概念,它是站在WindowManagerService角度上的一个抽象的概念,安卓中所有的视图都是通过窗口来呈现的,不管是活动,对话还是面包,只要有浏览的地方就一定有窗口。

这里需要注意的是,这个抽象的窗口概念和PhoneWindow这个类并不是同一个东西,PhoneWindow表示的是手机屏幕的抽象,它充当活动和DecorView之间的媒介,就算没有PhoneWindow也是可以展示查看的。

抛开一切,仅站在WindowManagerService的角度上,安卓的界面就是由一个个窗口层叠展现的,而窗户又是一个抽象的概念,它并不是实际存在的,它是以景观的形式存在,这个视图就是DecorView。

关于窗口这方面的内容,我们这里先了解一个大概

DecorView的概念

DecorView是整个窗口界面的最顶层视图,视图的测量,布局,绘制,事件分发都是由DecorView往下遍历这个景观树.DecorView作为顶级景观,一般情况下它内部会包含一个竖直方向的LinearLayout中,在这个的LinearLayout里面有上下两个部分(具体情况和的Android的版本及主题有关),上面是【标题栏】,下面是【内容栏】。在活动中我们通过的setContentView所设置的布局文件其实就是被加载到【内容栏】中的,而内容栏的ID是内容,因此指定布局的方法叫setContent()。

的ViewRoot的概念

的ViewRoot对应于ViewRootImpl类,它是连接的WindowManager和DecorView的纽带,查看的三大流程均是通过的ViewRoot来完成的。在ActivityThread中,当活动对象被创建完之后,会讲DecorView添加到窗口中,同时会创建对应的ViewRootImpl,并将ViewRootImpl和DecorView建立关联,并保存到WindowManagerGlobal对象中。

  1. WindowManagerGlobal.java 
  2.  
  3. root = new ViewRootImpl(view .getContext(),display);   
  4. root.setView(view ,wparams,panelParentView);  

Java的

查看的绘制流程是从的ViewRoot的performTraversals方法开始的,它经过测量,布局和绘制三个过程才能最终将一个视图绘制出来,大致流程如下图:

测量测量

为了更好地理解查看的测量过程,我们还需要理解MeasureSpec,它是搜索的一个内部类,它表示对查看的测量规格.MeasureSpec代表一个32位INT值,高2位代表SpecMode(测量模式),低30位代表SpecSize(测量大小),我们可以看看它的具体实现:

  1. MeasureSpec.java 
  2.  
  3. 公共静态 类MeasureSpec {    
  4.         private  static  final  int  MODE_SHIFT = 30; 
  5.         private  static  final  int  MODE_MASK = 0x3 << MODE_SHIFT; 
  6.  
  7.         / ** 
  8.           *未知模式: 
  9.           *父查看不对子查看有任何限制,子查看需要多大就多大 
  10.           * /  
  11.         公共静态 最终  诠释 Unknown = 0 << MODE_SHIFT;  
  12.  
  13.         / ** 
  14.           *精确模式: 
  15.           *父查看已经测量出子Viwe所需要的精确大小,这时候查看的最终大小 
  16.           *就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式 
  17.           * /  
  18.         public static  final  int  EXACTLY = 1 << MODE_SHIFT;  
  19.  
  20.         / ** 
  21.           * AT_MOST模式: 
  22.           *子查看的最终大小是父查看指定的SpecSize值,并且子查看的大小不能大于这个值, 
  23.           *即对应wrap_content这种模式 
  24.           * /  
  25.         public static  final  int  AT_MOST = 2 << MODE_SHIFT;  
  26.  
  27.         //将大小和模式打包成一个32位的INT 型数值 
  28.         //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小 
  29.         public static int  makeMeasureSpec(int size ,  int  mode){    
  30.             if(sUseBrokenMakeMeasureSpec){ 
  31.                 返回大小 +模式;  
  32.             }  else  { 
  33.                 返回 (size  &〜MODE_MASK)| (mode&MODE_MASK); 
  34.             } 
  35.         } 
  36.  
  37.         //将32位的MeasureSpec解包,返回SpecMode,测量模式 
  38.         public static int  getMode(int  measureSpec){   
  39.             return  (measureSpec&MODE_MASK); 
  40.         } 
  41.  
  42.         //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小 
  43.         public static int  getSize(int  measureSpec){   
  44.             return  (measureSpec&〜MODE_MASK); 
  45.         } 
  46.         // ... 
  47.     }  

Java的

MeasureSpec通过将SpecMode和SpecSize打包成一个INT值来避免过多的对象内存分配,并提供了打包和解包的方法。

SpecMode有三种类型,每一类都表示特殊的含义:

UNSPECIFIED

父容器不对视图有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态;

究竟

父容器已经检测出查看所需的精确大小,这个时候搜索的最终打消就是SpecSize所指定的值。它对应于的LayoutParams中的match_parent和具体数值这两种模式。

最多

父容器指定了一个可用大小即SpecSize,查看的大小不能大于这个值,具体是什么值要看不同观的具体实现。它对应于的LayoutParams中WRAP_CONTENT。

查看的MeasureSpec是由父容器的MeasureSpec和自己的的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有父类。在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小:

ViewGroup中的措施

  1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth,lp.width); 
  2.  
  3. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight,lp.height); 
  4.  
  5. performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);  

Java的

再看看getRootMeasureSpec方法:

  1. private  static int  getRootMeasureSpec(int  windowSize,  int  rootDimension){  
  2.         int  measureSpec; 
  3.         开关(rootDimension){ 
  4.  
  5.         case  ViewGroup.LayoutParams.MATCH_PARENT: 
  6.             //窗口无法调整大小。 强制 根  视图 windowSize。  
  7.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY); 
  8.             打破; 
  9.         案例 ViewGroup.LayoutParams.WRAP_CONTENT: 
  10.             //窗口可以调整大小 设置最大尺寸 根  视图。    
  11.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST); 
  12.             打破; 
  13.         默认: 
  14.             //窗口要   成为一门精确  的尺寸。  根  视图 是  尺寸。  
  15.             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,MeasureSpec.EXACTLY); 
  16.             打破; 
  17.         } 
  18.         返回 measureSpec; 
  19.     }  

Java的

通过以上代码,DecorView的MeasureSpec的产生过程就很明确了,因为DecorView是FrameLyaout的子类,属于ViewGroup中,对于ViewGroup中来说,除了完成自己的测量过程外,还会遍历去调用所有子元素的测量方法,各个子元素再递归去执行这个过程。和查看不同的是,一个ViewGroup是一个抽象类,他没有重写查看的onMeasure方法,这里很好理解,因为每个具体的ViewGroup中实现类的功能是不同的,如何测量应该让它自己决定,比如LinearLayout中和RelativeLayout的。

因此在具体的一个ViewGroup中需要遍历去测量子查看,这里我们看看一个ViewGroup中提供的测量子视图的measureChildWithMargins方法:

  1. 保护无效measureChildWithMargins(查看 孩子, 
  2.             int  parentWidthMeasureSpec,  int  widthUsed, 
  3.             int  parentHeightMeasureSpec,  int  heightUsed){ 
  4.         最终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.     }  

Java的

上述方法会对子元素进行测量,在调用子元素的测量方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码上看,子元素的MeasureSpec的创建与父容器的MeasureSpec和本身的的LayoutParams有关,此外和查看的保证金和父类的填充有关,现在看看getChildMeasureSpec的具体实现:

  1. ViewGroup.java 
  2.  
  3. public static int  getChildMeasureSpec(int  spec,  int  padding,  int  childDimension){     
  4.     int  specMode = MeasureSpec.getMode(spec); 
  5.     int  specSize = MeasureSpec.getSize(spec); 
  6.  
  7.     int size  = Math。max (0,specSize - padding);  
  8.  
  9.     int  resultSize = 0; 
  10.     int  resultMode = 0; 
  11.  
  12.     开关(specMode){ 
  13.     //父母 我们 施加了一个确切的  大小 
  14.     case  MeasureSpec.EXACTLY: 
  15.         if(childDimension> = 0){ 
  16.             resultSize = childDimension; 
  17.             resultMode = MeasureSpec.EXACTLY; 
  18.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  19.             //孩子要   是我们的  大小就这样吧。 
  20.             resultSize =  size 
  21.             resultMode = MeasureSpec.EXACTLY; 
  22.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  23.             //孩子想   确定自己的  大小不可能 
  24.             //比我们大 
  25.             resultSize =  size 
  26.             resultMode = MeasureSpec.AT_MOST; 
  27.         } 
  28.         打破; 
  29.  
  30.     //父母 我们 施加了最大的  尺寸 
  31.     case  MeasureSpec.AT_MOST: 
  32.         if(childDimension> = 0){ 
  33.             //孩子想要一个特定的  尺寸...就这样吧 
  34.             resultSize = childDimension; 
  35.             resultMode = MeasureSpec.EXACTLY; 
  36.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  37.             //孩子要   是我们的  规模,但我们的  规模 固定的。   
  38.             //约束孩子   比我们更大。  
  39.             resultSize =  size 
  40.             resultMode = MeasureSpec.AT_MOST; 
  41.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  42.             //孩子想   确定自己的  大小不可能 
  43.             //比我们大 
  44.             resultSize =  size 
  45.             resultMode = MeasureSpec.AT_MOST; 
  46.         } 
  47.         打破; 
  48.  
  49.     //家长问   看到我们想有多大  ,以 成为 
  50.     案例 MeasureSpec.UNSPECIFIED: 
  51.         if(childDimension> = 0){ 
  52.             //孩子想要一个特定的  尺寸...让他拥有它 
  53.             resultSize = childDimension; 
  54.             resultMode = MeasureSpec.EXACTLY; 
  55.         }  else  if(childDimension == LayoutParams.MATCH_PARENT){ 
  56.             //孩子要   是我们的  规模......发现   它有多大应该 
  57.             // be 
  58.             resultSize =  查看.sUseZeroUnspecifiedMeasureSpec?0:  尺寸
  59.             resultMode = MeasureSpec.UNSPECIFIED; 
  60.         }  else  if(childDimension == LayoutParams.WRAP_CONTENT){ 
  61.             //孩子想   确定自己的  大小....找到   如何 
  62.             //它应该是大的 
  63.             resultSize =  查看.sUseZeroUnspecifiedMeasureSpec?0:  尺寸
  64.             resultMode = MeasureSpec.UNSPECIFIED; 
  65.         } 
  66.         打破; 
  67.     } 
  68.     //没有检查ResourceType 
  69.     返回 MeasureSpec.makeMeasureSpec(resultSize,resultMode); 
  70.  

Java的

上述代码根据父类的MeasureSpec和自身的的LayoutParams创建子元素的MeasureSpec,具体过程同学们自行分析,最终的创建规则如下表:

ViewGroup中在遍历完子视图后,需要根据子元素的测量结果来决定自己最终的测量大小,并调用setMeasuredDimension方法保存测量宽高值。

  1. setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec,childState),heightSizeAndState); 

Java的

这里调用了resolveSizeAndState来确定最终的大小,主要是保证测量的大小不能超过父容器的最大剩余空间maxWidth,这里我们看看它里面的实现:

  1. public static int  resolveSizeAndState(int size ,  int  measureSpec,  int  childMeasuredState){    
  2.         final  int  specMode = MeasureSpec.getMode(measureSpec); 
  3.         final  int  specSize = MeasureSpec.getSize(measureSpec); 
  4.         最终  INT  结果; 
  5.         开关(specMode){ 
  6.             case  MeasureSpec.AT_MOST: 
  7.                 if(specSize <  size ){ 
  8.                     结果= specSize | MEASURED_STATE_TOO_SMALL; 
  9.                 }  else  { 
  10.                     结果=  大小
  11.                 } 
  12.                 打破; 
  13.             case  MeasureSpec.EXACTLY: 
  14.                 结果= specSize; 
  15.                 打破; 
  16.             案例 MeasureSpec.UNSPECIFIED: 
  17.             默认: 
  18.                 结果=  大小
  19.         } 
  20.         返回 结果| (childMeasuredState&MEASURED_STATE_MASK); 
  21.     }  

Java的

关于具体的ViewGroup的onMeasure过程这里不做分析,由于每种布局的测量方式不一样,不可能逐个分析,但在它们的onMeasure里面的步骤是有一定规律的:

1.根据各自的测量规则遍历儿童元素,调用getChildMeasureSpec方法得到孩子的measureSpec;

2.调用儿童的度量方法;

3.调用setMeasuredDimension确定最终的大小。

查看的措施

查看的测量过程由其测量方法来完成,测量方法是一个最终的类型的方法,这意味着子类不能重写此方法,在景观的措施方法里面会去调用onMeasure方法,我们这里只要看onMeasure的实现即可,如下:

  1. 查看.java 
  2.  
  3.     protected void onMeasure(int  widthMeasureSpec,  int  heightMeasureSpec){ 
  4.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec)) 
  5.                 getDefaultSize(getSuggestedMinimumHeight(),heightMeasureSpec)); 
  6.     }  

Java的

代码很简单,我们继续看看getDefaultSize方法的实现:

  1. 查看.java 
  2.  
  3.     public static int  getDefaultSize(int size ,  int  measureSpec){    
  4.         int  result =  size 
  5.         int  specMode = MeasureSpec.getMode(measureSpec); 
  6.         int  specSize = MeasureSpec.getSize(measureSpec); 
  7.  
  8.         开关(specMode){ 
  9.         案例 MeasureSpec.UNSPECIFIED: 
  10.             结果=  大小
  11.             打破; 
  12.         case  MeasureSpec.AT_MOST: 
  13.         case  MeasureSpec.EXACTLY: 
  14.             结果= specSize; 
  15.             打破; 
  16.         } 
  17.         返回 结果; 
  18.     } 

Java的

从上述代码可以得出,景观的宽/高由specSize决定,直接继承视图的自定义控件需要重写onMeasure方法并设置WRAP_CONTENT时的自身大小,否则在布局中使用WRAP_CONTENT就相当于使用match_parent。

上述就是查看的量度大致过程,在测量完成之后,通过getMeasuredWidth /高度方法就可以获得测量后的宽高,这个宽高一般情况下就等于查看的最终宽高了,因为搜索的布局布局的时候就是根据measureWidth /高度来设置宽高的,除非在布局中修改了测量值。

布局布局

布局的作用是一个ViewGroup用来确定子元素的位置,当ViewGroup中的位置被确定后,它在onLayout中会遍历所有的子元素并调用其布局方法。简单的来说就是,布局方法确定视图本身的位置,而onLayout方法则会确定所有子元素的位置。

先看看查看的布局方法:

  1. public  void layout(int  l,  int  t,  int  r,  int  b){ 
  2.         if((mPrivateFlags3&PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT)!= 0){ 
  3.             onMeasure(mOldWidthMeasureSpec,mOldHeightMeasureSpec); 
  4.             mPrivateFlags3&=〜PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 
  5.         } 
  6.  
  7.         int  oldL = mLeft; 
  8.         int  oldT = mTop; 
  9.         int  oldB = mBottom; 
  10.         int  oldR = mRight; 
  11.  
  12.         boolean changed = isLayoutModeOptical(mParent)? 
  13.                 setOpticalFrame(l,t,r,b):setFrame(l,t,r,b); 
  14.  
  15.         if(changed ||(mPrivateFlags&PFLAG_LAYOUT_REQUIRED)== PFLAG_LAYOUT_REQUIRED){ 
  16.             onLayout(改变,l,t,r,b); 
  17.  
  18.             if(shouldDrawRoundScrollbar()){ 
  19.                 if(mRoundScrollbarRenderer ==  null ){ 
  20.                     mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); 
  21.                 } 
  22.             }  else  { 
  23.                 mRoundScrollbarRenderer =  null 
  24.             } 
  25.  
  26.             mPrivateFlags&=〜PFLAG_LAYOUT_REQUIRED; 
  27.  
  28.             ListenerInfo li = mListenerInfo; 
  29.             if(li!=  null  && li.mOnLayoutChangeListeners!=  null ){ 
  30.                 ArrayList <OnLayoutChangeListener> listenersCopy = 
  31.                         (ArrayList的<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); 
  32.                 int  numListeners = listenersCopy。size (); 
  33.                 for  (int  i = 0; i <numListeners; ++ i){ 
  34.                     listenerCopy.get(i).onLayoutChange(this,l,t,r,b,oldL,oldT,oldR,oldB); 
  35.                 } 
  36.             } 
  37.         } 
  38.  
  39.         mPrivateFlags&=〜PFLAG_FORCE_LAYOUT; 
  40.         mPrivateFlags3 | = PFLAG3_IS_LAID_OUT; 
  41.     }  

因微信字数限制,请点击原文链接查看完整内容

总结

到这里,查看的措施,布局,绘制三大流程就说完了,这里做一下总结:

  • 如果是自定义的ViewGroup的话,需要重写onMeasure方法,在onMeasure方法里面遍历测量子元素,同理onLayout方法也是一样,最后实现的onDraw方法绘制自己;
  • 如果自定义视图的话,则需要从写onMeasure方法,处理WRAP_CONTENT的情况,不需要处理onLayout,最后实现的onDraw方法绘制自己; 

原文连接
原创粉丝点击