time 
设为首页】【收藏本站
当前位置: 主页 > 电脑网络 > 操作系统 > 嵌入式 > Android > Android View(二)视图绘制流程完全解析

Android View(二)视图绘制流程完全解析

时间:2014-06-20 11:20 点击:3643次 字体:[ ]




上一篇文章中,我带着大家一起剖析了一下LayoutInflater的工作原理,可以算是对View进行深入了解的第一步吧。那么本篇文章中,我们将继续对View进行深入探究,看一看它的绘制流程到底是什么样的。如果你还没有看过我的上一篇文章,可以先去阅读http://www.fengfly.com/plus/view-215111-1.html

相信每个Android程序员都知道,我们每天的开发工作当中都在不停地跟View打交道,Android中的任何一个布局、任何一个控件其实都是直接或间接继承自View的,如TextView、Button、ImageView、ListView等。这些控件虽然是Android系统本身就提供好的,我们只需要拿过来使用就可以了,但你知道它们是怎样被绘制到屏幕上的吗?多知道一些总是没有坏处的,那么我们赶快进入到本篇文章的正题内容吧。

要知道,任何一个视图都不可能凭空突然出现在屏幕上,它们都是要经过非常科学的绘制流程后才能显示出来的。每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。

一. onMeasure()

measure是测量的意思,那么onMeasure()方法顾名思义就是用于测量视图的大小的。View系统的绘制流程会从ViewRoot的performTraversals()方法中开始,在其内部调用View的measure()方法。measure()方法接收两个参数,widthMeasureSpec和heightMeasureSpec,这两个值分别用于确定视图的宽度和高度的规格和大小。

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
 

1. EXACTLY

表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

2. AT_MOST

表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。

3. UNSPECIFIED

表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。

那么你可能会有疑问了,widthMeasureSpec和heightMeasureSpec这两个值又是从哪里得到的呢?通常情况下,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了,观察performTraversals()方法可以发现如下代码:

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

可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:

 

  1. private int getRootMeasureSpec(int windowSize, int rootDimension) {  
  2.     int measureSpec;  
  3.     switch (rootDimension) {  
  4.     case ViewGroup.LayoutParams.MATCH_PARENT:  
  5.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);  
  6.         break;  
  7.     case ViewGroup.LayoutParams.WRAP_CONTENT:  
  8.         measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);  
  9.         break;  
  10.     default:  
  11.         measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);  
  12.         break;  
  13.     }  
  14.     return measureSpec;  
  15. }  

可以看到,这里使用了MeasureSpec.makeMeasureSpec()方法来组装一个MeasureSpec,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。

介绍了这么多MeasureSpec相关的内容,接下来我们看下View的measure()方法里面的代码吧,如下所示:

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.     if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||  
  3.             widthMeasureSpec != mOldWidthMeasureSpec ||  
  4.             heightMeasureSpec != mOldHeightMeasureSpec) {  
  5.         mPrivateFlags &= ~MEASURED_DIMENSION_SET;  
  6.         if (ViewDebug.TRACE_HIERARCHY) {  
  7.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);  
  8.         }  
  9.         onMeasure(widthMeasureSpec, heightMeasureSpec);  
  10.         if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {  
  11.             throw new IllegalStateException("onMeasure() did not set the" 
  12.                     + " measured dimension by calling" 
  13.                     + " setMeasuredDimension()");  
  14.         }  
  15.         mPrivateFlags |= LAYOUT_REQUIRED;  
  16.     }  
  17.     mOldWidthMeasureSpec = widthMeasureSpec;  
  18.     mOldHeightMeasureSpec = heightMeasureSpec;  
  19. }  

注意观察,measure()这个方法是final的,因此我们无法在子类中去重写这个方法,说明Android是不允许我们改变View的measure框架的。然后在第9行调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:

  1. 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.     switch (specMode) {  
  6.     case MeasureSpec.UNSPECIFIED:  
  7.         result = size;  
  8.         break;  
  9.     case MeasureSpec.AT_MOST:  
  10.     case MeasureSpec.EXACTLY:  
  11.         result = specSize;  
  12.         break;  
  13.     }  
  14.     return result;  
  15. }  

这里传入的measureSpec是一直从measure()方法中传递过来的。然后调用MeasureSpec.getMode()方法可以解析出specMode,调用MeasureSpec.getSize()方法可以解析出specSize。接下来进行判断,如果specMode等于AT_MOST或EXACTLY就返回specSize,这也是系统默认的行为。之后会在onMeasure()方法中调用setMeasuredDimension()方法来设定测量出的大小,这样一次measure过程就结束了。



本文地址 : http://www.fengfly.com/plus/view-215112-1.html
标签: Android View 视图绘制流程
------分隔线----------------------------
最新评论 查看所有评论
发表评论 查看所有评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
验证码: