Android View 的绘制流程之 Measure 过程详解 (一)Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View

Android View 的绘制流程之 Measure 过程详解 (一)Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View ,第1张

概述View 的绘制系列文章: Android View 绘制流程之 DecorView 与 ViewRootImpl Android View 的绘制流程之 Measure 过程详解 (一) Andro VIEw 的绘制系列文章:Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解

 

概述

上一篇 AndroID VIEw 绘制流程之 DecorVIEw 与 VIEwRootImpl 分析了在调用 setContentVIEw 之后,DecorVIEw 是如何与 activity 关联在一起的,最后讲到了 VIEwRootImpl 开始绘制的逻辑。本文接着上篇,继续往下讲,开始分析 vIEw 的绘制流程。

上文说到了调用 performTraversals 进行绘制,由于 performTraversals 方法比较长,看一个简化版:

// VIEwRootImpl 类private voID performTraversals() {     这个方法代码非常多,但是重点就是执行这三个方法     执行测量    performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec);     执行布局(VIEwGroup)中才会有    performlayout(lp,mWIDth,mHeight);     执行绘制    performDraw();}

其流程具体如下:

 VIEw的整个绘制流程可以分为以下三个阶段:

measure: 判断是否需要重新计算 VIEw 的大小,需要的话则计算;

layout: 判断是否需要重新计算 VIEw 的位置,需要的话则计算;

draw: 判断是否需要重新绘制 VIEw,需要的话则重绘制。

@H_403_103@MeasureSpec

在介绍绘制前,先了解下 MeasureSpec。MeasureSpec 封装了父布局传递给子布局的布局要求,它通过一个 32 位 int 类型的值来表示,该值包含了两种信息,高两位表示的是 SpecMode(测量模式),低 30 位表示的是 Specsize(测量的具体大小)。下面通过注释的方式来分析来类:

/**   * 三种SpecMode:  * 1.UnspecIFIED  * 父 VIEwGroup 没有对子VIEw施加任何约束,子 vIEw 可以是任意大小。这种情况比较少见,主要用于系统内部多次measure的情形,
* 用到的一般都是可以滚动的容器中的子vIEw,比如ListVIEw、GrIDVIEw、RecyclerVIEw中某些情况下的子vIEw就是这种模式。
* 一般来说,我们不需要关注此模式。 * 2.EXACTLY  * 该 vIEw 必须使用父 VIEwGroup 给其指定的尺寸。对应 match_parent 或者具体数值(比如30dp) * 3.AT_MOST  * 该 VIEw 最大可以取父VIEwGroup给其指定的尺寸。对应wrap_content *   * MeasureSpec使用了二进制去减少对象的分配。  
*/  public class MeasureSpec {           进位大小为2的30次方(int的大小为32位,所以进位30位就是要使用int的最高位和第二高位也就是32和31位做标志位)          private static final int MODE_SHIFT = 30;            运算遮罩,0x3为16进制,10进制为3,二进制为11。3向左进位30,就是11 00000000000(11后跟30个0)           (遮罩的作用是用1标注需要的值,0标注不要的值。因为1与任何数做与运算都得任何数,0与任何数做与运算都得0)          int MODE_MASK  = 0x3 << MODE_SHIFT;            0向左进位30,就是00 00000000000(00后跟30个0)          int UnspecIFIED = 0 << MODE_SHIFT;           1向左进位30,就是01 00000000000(01后跟30个0)          int EXACTLY     = 1 << 2向左进位30,就是10 00000000000(10后跟30个0)          int AT_MOST     = 2 <<          * 根据提供的size和mode得到一个详细的测量结果                     第一个return:         measureSpec = size + mode;   (注意:二进制的加法,不是十进制的加法!)           这里设计的目的就是使用一个32位的二进制数,32和31位代表了mode的值,后30位代表size的值           例如size=100(4),mode=AT_MOST,则measureSpec=100+10000...00=10000..00100                    第二个return:         size &; ~MODE_MASK就是取size 的后30位,mode &amp; MODE_MASK就是取mode的前两位,最后执行或运算,得出来的数字,前面2位包含代表mode,后面30位代表size        int makeMeasureSpec(int size, int mode) {              if (sUsebrokenMakeMeasureSpec) {                return size + mode;            } else {                return (size & ~MODE_MASK) | (mode &  MODE_MASK);            }        }                     * 获得SpecMode          mode = measureSpec &amp; MODE_MASK;           MODE_MASK = 11 00000000000(11后跟30个0),原理是用MODE_MASK后30位的0替换掉measureSpec后30位中的1,再保留32和31位的mode值。           例如10 00..00100 & 11 00..00(11后跟30个0) = 10 00..00(AT_MOST),这样就得到了mode的值          int getMode( measureSpec) {              return (measureSpec & MODE_MASK);          }                     * 获得Specsize           size = measureSpec &  ~MODE_MASK;           原理同上,不过这次是将MODE_MASK取反,也就是变成了00 111111(00后跟30个1),将32,31替换成0也就是去掉mode,保留后30位的size          int getSize(return (measureSpec &  ~MODE_MASK);          }  }  

顺便提下 MATCH_PARENT 和 WRAP_CONTENT 这两个代表的值,分别是 -1 和 -2。

                * Special value for the height or wIDth requested by a VIEw.         * MATCH_PARENT means that the vIEw wants to be as big as its parent,* minus the parent's padding,if any. Introduced in API Level 8.         */        public static final int MATCH_PARENT = -1;                 * Special value for the height or wIDth requested by a VIEw.         * WRAP_CONTENT means that the vIEw wants to be just large enough to fit         * its own internal content,taking its own padding into account.         int WRAP_CONTENT = -2;
@H_403_103@DecorvIEw 尺寸的确定

在 performTraversals 中,首先是要确定 DecorVIEw 的尺寸。只有当 DecorVIEw 尺寸确定了,其子 VIEw 才可以知道自己能有多大。具体是如何去确定的,可以看下面的代码:

Activity窗口的宽度和高度 desireDWindowWIDth; desireDWindowHeight;...用来保存窗口宽度和高度,来自于全局变量mWinFrame,这个mWinFrame保存了窗口最新尺寸Rect frame = mWinFrame;构造方法里mFirst赋值为true,意思是第一次执行遍历吗     (mFirst) {    是否需要重绘    mFullRedrawNeeded = true;    是否需要重新确定Layout    mLayoutRequested = ;        // 这里又包含两种情况:是否包括状态栏        判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的DecorvIEw的高度和宽度     (shouldUsedisplaySize(lp)) {         NOTE -- system code,won't try to do compat mode.        Point size = new Point();        mdisplay.getRealSize(size);        desireDWindowWIDth = size.x;        desireDWindowHeight = size.y;    }  {        宽度和高度为整个屏幕的值        Configuration config = mContext.getResources().getConfiguration();        desireDWindowWIDth = diptopx(config.screenWIDthDp);        desireDWindowHeight = diptopx(config.screenHeightDp);    }    ... else{            // 这是window的长和宽改变了的情况,需要对改变的进行数据记录            如果不是第一次进来这个方法,它的当前宽度和高度就从之前的mWinFrame获取        desireDWindowWIDth = frame.wIDth();        desireDWindowHeight = frame.height();                 * mWIDth和mHeight是由WindowManagerService服务计算出的窗口大小,         * 如果这次测量的窗口大小与这两个值不同,说明WMS单方面改变了窗口的尺寸         if (desireDWindowWIDth != mWIDth || desireDWindowHeight != mHeight) {            if (DEBUG_ORIENTATION) Log.v(mTag,"VIEw " + host + " resized to: " + frame);            需要进行完整的重绘以适应新的窗口尺寸            mFullRedrawNeeded = ;            需要对控件树进行重新布局            mLayoutRequested = window窗口大小改变            windowsizeMayChange = ;        } }    ...    // 进行预测量     (layoutRequested){        ...         (mFirst) {             视图窗口当前是否处于触摸模式。            mAttachInfo.mIntouchMode = !mAddedtouchMode;            确保这个Window的触摸模式已经被设置            ensuretouchModeLocally(mAddedtouchMode);        }  {            六个if语句,判断insects值和上一次比有什么变化,不同的话就改变insetsChanged            insects值包括了一些屏幕需要预留的区域、记录一些被遮挡的区域等信息            if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {                    insetsChanged = ;            }            ...                      //  这里有一种情况,我们在写dialog时,会手动添加布局,当设定宽高为Wrap_content时,会把屏幕的宽高进行赋值,给出尽量长的宽度                                     * 如果当前窗口的根布局的wIDth或height被指定为 WRAP_CONTENT 时,             * 比如Dialog,那我们还是给它尽量大的长宽,这里是将屏幕长宽赋值给它             */            if (lp.wIDth == VIEwGroup.LayoutParams.WRAP_CONTENT                    || lp.height == VIEwGroup.LayoutParams.WRAP_CONTENT) {                windowsizeMayChange = ;                判断要绘制的窗口是否包含状态栏,有就去掉,然后确定要绘制的DecorvIEw的高度和宽度                 (shouldUsedisplaySize(lp)) {                     Point();                    mdisplay.getRealSize(size);                    desireDWindowWIDth = size.x;                    desireDWindowHeight = size.y;                }  {                    Configuration config = res.getConfiguration();                    desireDWindowWIDth = diptopx(config.screenWIDthDp);                    desireDWindowHeight = diptopx(config.screenHeightDp);                }            }        }    }}

这里主要是分两步走:

如果是第一次测量,那么根据是否有状态栏,来确定是直接使用屏幕的高度,还是真正的显示区高度。

如果不是第一次,那么从 mWinFrame 获取,并和之前保存的长宽高进行比较,不相等的话就需要重新测量确定高度。

当确定了 DecorVIEw 的具体尺寸之后,然后就会调用 measureHIErarchy 来确定其 MeasureSpec :

  Ask host how big it wants to be  windowsizeMayChange |= measureHIErarchy(host,lp,res,desireDWindowWIDth,desireDWindowHeight); 

其中 host 就是 DecorVIEw,lp 是 wm 在添加时候传给 DecorVIEw 的,最后两个就是刚刚确定显示宽高 ,看下方法的具体逻辑 :

    boolean measureHIErarchy(final VIEw host,final WindowManager.LayoutParams lp,final Resources res,1)">int desireDWindowWIDth,1)"> desireDWindowHeight) {         chilDWIDthMeasureSpec;         childHeightmeasureSpec;        boolean windowsizeMayChange = false;boolean goodMeasure = ;
     // 说明是 dialog
VIEwGroup.LayoutParams.WRAP_CONTENT) { On large screens,we don't want to allow dialogs to just stretch to fill the entire wIDth of the screen to display one line of text. First try doing the layout at a smaller size to see if it will fit. final displayMetrics packageMetrics = res.getdisplayMetrics(); res.getValue(com.androID.internal.R.dimen.config_prefDialogWIDth,mTmpValue,1)">); int baseSize = 0       // 获取一个基本的尺寸 if (mTmpValue.type == TypedValue.TYPE_DIMENSION) { baseSize = ()mTmpValue.getDimension(packageMetrics); } if (DEBUG_DIALOG) Log.v(mTag,"Window " + mVIEw + ": baseSize=" + baseSize + ",desireDWindowWIDth=" + desireDWindowWIDth);
       // 如果大于基本尺寸
if (baseSize != 0 && desireDWindowWIDth > baseSize) { chilDWIDthMeasureSpec = getRootMeasureSpec(baseSize,lp.wIDth); childHeightmeasureSpec = getRootMeasureSpec(desireDWindowHeight,lp.height); performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); host.getMeasuredHeight() + ") from wIDth spec: " + MeasureSpec.toString(chilDWIDthMeasureSpec) + " and height spec: " + MeasureSpec.toString(childHeightmeasureSpec));
          // 判断测量是否准确
if ((host.getMeasureDWIDthAndState()&VIEw.MEASURED_STATE_TOO_SMALL) == 0) { goodMeasure = ; } { DIDn't fit in that size... try expanding a bit. baseSize = (baseSize+desireDWindowWIDth)/2; performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec); ); ) { ); goodMeasure = ; } } } }     // 这里就是一般 DecorVIEw 会走的逻辑 goodMeasure) { chilDWIDthMeasureSpec = getRootMeasureSpec(desireDWindowWIDth,lp.wIDth); childHeightmeasureSpec = performMeasure(chilDWIDthMeasureSpec,childHeightmeasureSpec);
       // 与之前的尺寸进行对比,看看是否相等,不想等,说明尺寸可能发生了变化
if (mWIDth != host.getMeasureDWIDth() || mHeight != host.getMeasuredHeight()) { windowsizeMayChange = ; } } return windowsizeMayChange; }

上面主要主要做的就是来确定父 VIEw 的 MeasureSpec。但是分了两种不同类型:

如果宽是 WRAP_CONTENT 类型,说明这是 dialog,会有一些针对 dialog 的处理,最终会调用 performMeasure 进行测量;

对于一般 Activity 的尺寸,会调用  getRootMeasureSpec MeasureSpec 。

下面看下 DecorVIEw MeasureSpec 的计算方法:

    int getRootMeasureSpec(int windowsize,1)"> rootDimension) {         measureSpec;        switch (rootDimension) {        case VIEwGroup.LayoutParams.MATCH_PARENT:             Window can't resize. Force root vIEw to be windowsize.            measureSpec = MeasureSpec.makeMeasureSpec(windowsize,MeasureSpec.EXACTLY);            break;         VIEwGroup.LayoutParams.WRAP_CONTENT:             Window can resize. Set max size for root vIEw.            measureSpec =default:             Window wants to be an exact size. Force root vIEw to be that size.            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension,1)">;        }        return measureSpec;    }

 该方法主要是根据 VIEw 的 MeasureSpec 是根据宽高的参数来划分的。

MATCH_PARENT :精确模式,大小就是窗口的大小;

WRAP_CONTENT :最大模式,大小不定,但是不能超过窗口的大小;

固定大小:精确模式,大小就是指定的具体宽高,比如100dp。

对于 DecorVIEw 来说就是走第一个 case,到这里 DecorVIEw 的 MeasureSpec 就确定了,从 MeasureSpec 可以得出 DecorVIEw 的宽高的约束信息。

@H_403_103@获取子 vIEw 的 MeasureSpec

当父 VIEwGroup 对子 VIEw 进行测量时,会调用 VIEw 类的 measure 方法,这是一个 final 方法,无法被重写。VIEwGroup 会传入自己的 wIDthMeasureSpec 和  heightmeasureSpec,分别表示父 VIEw 对子 VIEw 的宽度和高度的一些限制条件。尤其是当 VIEwGroup 是 WRAP_CONTENT 的时候,需要优先测量子 VIEw,只有子 VIEw 宽高确定,VIEwGroup 才能确定自己到底需要多大的宽高。

当 DecorVIEw 的 MeasureSpec 确定以后,VIEwRootImpl 内部会调用 performMeasure 方法:

  voID performMeasure(int chilDWIDthMeasureSpec,1)"> childHeightmeasureSpec) {        if (mVIEw == null) {            ;        }        Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure");        try {            mVIEw.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec);        } finally {            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }    }

该方法传入的是对 DecorVIEw 的 MeasureSpec,其中 mVIEw 就是 DecorVIEw 的实例,接下来看 measure() 的具体逻辑:

 * 调用这个方法来算出一个VIEw应该为多大。参数为父VIEw对其宽高的约束信息。 * 实际的测量工作在onMeasure()方法中进行 */voID measure(int wIDthMeasureSpec,1)"> heightmeasureSpec) {  ......
  // Suppress sign extension for the low bytes   long key = (long) wIDthMeasureSpec << 32 | (long) heightmeasureSpec & 0xffffffffL;   if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局   比如调用VIEw.requestLayout()会在mPrivateFlags中加入此标记  boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;  boolean specChanged = wIDthMeasureSpec != molDWIDthMeasureSpec      || heightmeasureSpec != moldHeightmeasureSpec;  boolean isspecExactly = MeasureSpec.getMode(wIDthMeasureSpec) == MeasureSpec.EXACTLY      && MeasureSpec.getMode(heightmeasureSpec) == MeasureSpec.EXACTLY;  boolean matchesSpecsize = getMeasureDWIDth() == MeasureSpec.getSize(wIDthMeasureSpec)      && getMeasuredHeight() == MeasureSpec.getSize(heightmeasureSpec);  boolean needsLayout = specChanged      && (sAlwaysRemeasureExactly || !isspecExactly || !matchesSpecsize);   需要重新布局    if (forceLayout || needsLayout) {
    // first clears the measured dimension flag 标记为未测量状态
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  // 对阿拉伯语、希伯来语等从右到左书写、布局的语言进行特殊处理
resolveRtlPropertIEsIfNeeded();
 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是     忽略缓存,则调用onMeasure()重新进行测量工作    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);    if (cacheIndex < 0 || sIgnoreMeasureCache) {       measure ourselves,this should set the measured dimension flag back      onMeasure(wIDthMeasureSpec,heightmeasureSpec);      . . .    }  {       缓存命中,直接从缓存中取值即可,不必再测量      long value = mMeasureCache.valueAt(cacheIndex);       Casting a long to int drops the high 32 bits,no mask needed      setMeasuredDimensionRaw((int) (value >> 32),() value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
   // 如果自定义的VIEw重写了onMeasure方法,但是没有调用setMeasuredDimension()方法就会在这里抛出错误;
   // flag not set,setMeasuredDimension() was not invoked,we raise
   // an exception to warn the developer
   if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
   throw new IllegalStateException("VIEw with ID " + getID() + ": "
  + getClass().getname() + "#onMeasure() dID not set the"
  + " measured dimension by calling"
  + " setMeasuredDimension()");
   } 
      //到了这里,VIEw已经测量完了并且将测量的结果保存在VIEw的mMeasureDWIDth和mMeasuredHeight中,将标志位置为可以layout的状态

    mPrivateFlags |= PFLAG_LAYOUT_required;

  }  molDWIDthMeasureSpec = wIDthMeasureSpec;  moldHeightmeasureSpec = heightmeasureSpec;
// 保存到缓存中 mMeasureCache.put(key,((
long) mMeasureDWIDth) << 32 | (long) mMeasuredHeight & 0xffffffffL); suppress sign extension}

 这里要注意的是,这是一个 final 方法,不能被继承。这个方法只在 VIEw 类里面。总结一下 measure() 都干了什么事:

调用 VIEw.measure()方法时 VIEw 并不是立即就去测量,而是先判断一下要不要进行测量 *** 作,如果没必要,那么 VIEw 就不需要重新测量了,避免浪费时间资源

如果需要测量,在测量之前,会先判断是否存在缓存,存在直接从缓存中获取就可以了,再调用一下 setMeasuredDimensionRaw 方法,将从缓存中读到的测量结果保存到成员变量 mMeasureDWIDth 和 mMeasuredHeight 中。

如果不能从 mMeasureCache 中读到缓存过的测量结果,调用 onMeasure() 方法去完成实际的测量工作,并且将尺寸限制条件 wIDthMeasureSpec 和 heightmeasureSpec 传递给 onMeasure() 方法。关于 onMeasure() 方法,会在下面详细介绍。

将结果保存到 mMeasureDWIDth 和 mMeasuredHeight 这两个成员变量中,同时缓存到成员变量 mMeasureCache 中,以便下次执行 measure() 方法时能够从其中读取缓存值。

需要说明的是,VIEw 有一个成员变量 mPrivateFlags,用以保存 VIEw 的各种状态位,在测量开始前,会将其设置为未测量状态,在测量完成后会将其设置为已测量状态。

DecorVIEw 是 FrameLayout 子类,这时候应该去看 FrameLayout 中的 onMeasure() 方法,代码具体如下:

 protected voID onMeasure( heightmeasureSpec) {
     // 获取子vIEw的个数
int count = getChildCount(); boolean measureMatchParentChildren = MeasureSpec.getMode(wIDthMeasureSpec) != MeasureSpec.EXACTLY || MeasureSpec.getMode(heightmeasureSpec) != MeasureSpec.EXACTLY; mMatchParentChildren.clear(); int maxHeight = 0int maxWIDth = 0int childState = 0for (int i = 0; i < count; i++final VIEw child = getChildAt(i);
       // mMeasureAllChildren 默认为FALSE,表示是否全部子 vIEw 都要测量,子vIEw不为GONE就要测量
if (mMeasureAllChildren || child.getVisibility() != GONE) {
// 测量子vIEw measureChilDWithmargins(child,wIDthMeasureSpec,
0,heightmeasureSpec,0);
// 获取子vIEw的布局参数
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
// 记录子vIEw的最大宽度和高度 maxWIDth
= Math.max(maxWIDth,child.getMeasureDWIDth() + lp.leftmargin + lp.rightmargin); maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topmargin + lp.bottommargin); childState = combineMeasuredStates(childState,child.getMeasuredState());
// 记录所有跟父布局有着相同宽或高的子vIEw
(measureMatchParentChildren) { if (lp.wIDth == LayoutParams.MATCH_PARENT || lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } Account for padding too 子vIEw的最大宽高计算出来后,还要加上父VIEw自身的padding maxWIDth += getpaddingleftWithForeground() + getpaddingRightWithForeground(); maxHeight += getpaddingtopWithForeground() + getpaddingBottomWithForeground(); Check against our minimum height and wIDth maxHeight = Check against our foreground's minimum height and wIDth final Drawable drawable = getForeground(); if (drawable != ) { maxHeight =     // 确定父 vIEw 的宽高 setMeasuredDimension(resolveSizeAndState(maxWIDth,childState),resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT)); count = mMatchParentChildren.size(); if (count > 1) { mMatchParentChildren.get(i); final marginLayoutParams lp = (marginLayoutParams) child.getLayoutParams(); chilDWIDthMeasureSpec;
          // 如果子vIEw的宽是MATCH_PARENT,那么宽度 = 父vIEw的宽 - 父padding - 子margin
LayoutParams.MATCH_PARENT) { int wIDth = Math.max(0,getMeasureDWIDth() - getpaddingleftWithForeground() - getpaddingRightWithForeground() - lp.leftmargin - lp.rightmargin); chilDWIDthMeasureSpec = MeasureSpec.makeMeasureSpec( wIDth,MeasureSpec.EXACTLY); } { chilDWIDthMeasureSpec = getChildMeasureSpec(wIDthMeasureSpec,getpaddingleftWithForeground() + getpaddingRightWithForeground() + lp.leftmargin + lp.rightmargin,lp.wIDth); } childHeightmeasureSpec;
          // 同理
if (lp.height ==int height = Math.max(0 getpaddingBottomWithForeground() - lp.topmargin - lp.bottommargin); childHeightmeasureSpec = MeasureSpec.makeMeasureSpec( height,1)"> { childHeightmeasureSpec = getChildMeasureSpec(heightmeasureSpec,getpaddingtopWithForeground() + getpaddingBottomWithForeground() + lp.topmargin + lp.bottommargin,lp.height); } child.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec); } } }

FrameLayout 是 VIEwGroup 的子类,后者有一个 VIEw[] 类型的成员变量 mChildren,代表了其子 VIEw 集合。通过 getChildAt(i) 能获取指定索引处的子 VIEw,通过 getChildCount() 可以获得子 VIEw 的总数。

在上面的源码中,首先调用 measureChilDWithmargins() 方法对所有子 VIEw 进行了一遍测量,并计算出所有子 VIEw 的最大宽度和最大高度。而后将得到的最大高度和宽度加上padding,这里的 padding 包括了父 VIEw 的 padding 和前景区域的 padding。然后会检查是否设置了最小宽高,并与其比较,将两者中较大的设为最终的最大宽高。最后,若设置了前景图像,我们还要检查前景图像的最小宽高。

经过了以上一系列步骤后,我们就得到了 maxHeight 和 maxWIDth 的最终值,表示当前容器 VIEw 用这个尺寸就能够正常显示其所有子 VIEw(同时考虑了 padding 和 margin )。而后我们需要调用 resolveSizeAndState() 方法来结合传来的 MeasureSpec 来获取最终的测量宽高,并保存到 mMeasureDWIDth 与 mMeasuredHeight 成员变量中。

如果存在一些子 VIEw 的宽或高是 MATCH_PARENT,那么需要等父 VIEw 的尺寸计算出来后,重新计算这些子 VIEw 的 MeasureSpec,再来测量这些子 vIEw 的宽高。

这里提醒我们在自定义 VIEw 的时候需要考虑你的子 VIEw 是不是和你自定义的 VIEw 的大小是一样,如果一样,就需要等自定义 VIEw 的大小确定了,再重新测量一遍。

下面看看 measureChilDWithmargins() 方法具体逻辑:

     * Ask one of the children of this vIEw to measure itself,taking into     * account both the MeasureSpec requirements for this vIEw and its padding     * and margins. The child must have marginLayoutParams The heavy lifting is     * done in getChildMeasureSpec.     *     * @param child The child to measure     *  parentWIDthMeasureSpec The wIDth requirements for this vIEw     *  wIDthUsed Extra space that has been used up by the parent     *        horizontally (possibly by other children of the parent)     *  parentHeightmeasureSpec The height requirements for this vIEw     *  heightUsed Extra space that has been used up by the parent     *        vertically (possibly by other children of the parent)     */     measureChilDWithmargins(VIEw child,1)">int parentWIDthMeasureSpec,1)"> wIDthUsed,1)">int parentHeightmeasureSpec,1)"> heightUsed) {         (marginLayoutParams) child.getLayoutParams();        int chilDWIDthMeasureSpec = getChildMeasureSpec(parentWIDthMeasureSpec,mpaddingleft + mpaddingRight + lp.leftmargin + lp.rightmargin                        +int childHeightmeasureSpec = getChildMeasureSpec(parentHeightmeasureSpec,mpaddingtop + mpaddingBottom + lp.topmargin + lp.bottommargin                        + heightUsed,lp.height);        child.measure(chilDWIDthMeasureSpec,childHeightmeasureSpec);    }

 该方法主要是获取子 VIEw 的 MeasureSpec,然后调用 child.measure() 来完成子 VIEw 的测量。下面看看子 VIEw 获取 MeasureSpec 的具体逻辑:

 int getChildMeasureSpec(int spec,1)">int padding,1)"> childDimension) {
// 父 vIEw 的 mode 和 size int specMode = MeasureSpec.getMode(spec); int specsize = MeasureSpec.getSize(spec);     // 去掉 padding int size = Math.max(0,specsize - padding); int resultSize = 0int resultMode = 0 (specMode) { Parent has imposed an exact size on us 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 = LayoutParams.WRAP_CONTENT) { Child wants to determine its own size. It can't be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent has imposed a maximum size on us MeasureSpec.AT_MOST: Child wants a specific size... so be it resultSize = Child wants to be our size,but our size is not fixed. Constrain child to not be bigger than us. resultSize = MeasureSpec.AT_MOST; } Parent asked to see how big we want to be MeasureSpec.UnspecIFIED: Child wants a specific size... let him have it resultSize = Child wants to be our size... find out how big it should be resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 : size; resultMode = MeasureSpec.UnspecIFIED; } Child wants to determine its own size.... find out how big it should be resultSize = VIEw.sUseZeroUnspecifIEdMeasureSpec ? 0 MeasureSpec.UnspecIFIED; } noinspection ResourceType MeasureSpec.makeMeasureSpec(resultSize,resultMode); }

 方法清楚展示了普通 VIEw 的 MeasureSpec 的创建规则,每个 VIEw 的 MeasureSpec 状态量由其直接父 VIEw 的 MeasureSpec 和 VIEw 自身的属性 LayoutParams (LayoutParams 有宽高尺寸值等信息)共同决定。

从上面的代码可以知道,返回 VIEw 的 MeasureSpec 大致可以分为一下机制情况:

子 VIEw 为具体的宽/高,那么 VIEw 的 MeasureSpec 都为 LayoutParams 中大小。

子 VIEw 为 match_parent,父元素为精度模式(EXACTLY),那么 VIEw 的 MeasureSpec 也是精准模式他的大小不会超过父容器的剩余空间。

子 VIEw 为 wrap_content,不管父元素是精准模式还是最大化模式(AT_MOST),VIEw 的 MeasureSpec 总是为最大化模式并且大小不超过父容器的剩余空间。

父容器为 UnspecIFIED 模式主要用于系统多次 Measure 的情形,一般我们不需要关心。

总结为下表:

 VIEw.measure()  代码逻辑前面已经分析过了,最终会调用 onMeasuere 方法,下面看下 VIEw.onMeasuere() 的代码:

 protected int heightmeasureSpec) {        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWIDth(),wIDthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(),heightmeasureSpec));    }

上面方法中调用了 方法中调用了 setMeasuredDimension()方法,setMeasuredDimension()又调用了 getDefaultSize() 方法。getDefaultSize() 又调用了getSuggestedMinimumWIDth()和 getSuggestedMinimumHeight(),那反向研究一下,先看下 getSuggestedMinimumWIDth() 方法  (getSuggestedMinimumHeight() 原理 getSuggestedMinimumWIDth() 跟一样)。 

  getSuggestedMinimumWIDth() {        return (mBackground == null) ? mMinWIDth : max(mMinWIDth,mBackground.getMinimumWIDth());    }

 源码很简单,如果 VIEw 没有背景,就直接返回 VIEw 本身的最小宽度 mMinWIDth;如果给 VIEw 设置了背景,就取 VIEw 本身的最小宽度 mMinWIDth 和背景的最小宽度的最大值.

那么 mMinWIDth 是哪里来的?搜索下源码就可以知道,VIEw 的最小宽度 mMinWIDth 可以有两种方式进行设置:

第一种是在 VIEw 的构造方法中进行赋值的,VIEw 通过读取 XML 文件中VIEw设置的 minWIDth 属性来为 mMinWIDth 赋值
 R.styleable.VIEw_minWIDth:     mMinWIDth = a.getDimensionPixelSize(attr,1)">);     break;
 第二种是在调用 VIEw 的 setMinimumWIDth 方法为 mMinWIDth 赋值
voID setMinimumWIDth( minWIDth) {    mMinWIDth = minWIDth;    requestLayout();}

 下面看下 getDefaultSize() 的代码逻辑:

    int getDefaultSize(int size,1)"> measureSpec) {        int result = size;         MeasureSpec.getMode(measureSpec);         MeasureSpec.getSize(measureSpec);         MeasureSpec.UnspecIFIED:            result = size;             MeasureSpec.AT_MOST:         MeasureSpec.EXACTLY:            result = specsize;             result;    }

从注释可以看出,getDefaultSize()这个测量方法并没有适配 wrap_content 这一种布局模式,只是简单地将 wrap_content 跟 match_parent 等同起来。

到了这里,我们要注意一个问题:

getDefaultSize()方法中 wrap_content 和 match_parent 属性的效果是一样的,而该方法是 VIEw 的 onMeasure()中默认调用的,也就是说,对于一个直接继承自 VIEw 的自定义 VIEw 来说,它的 wrap_content 和 match_parent 属性是一样的效果,因此如果要实现自定义 VIEw 的 wrap_content,则要重写 onMeasure() 方法,对 wrap_content 属性进行处理。

如何处理呢?也很简单,代码如下所示:

protected voID onMeasure(int wIDthMeasureSpec,1)"> heightmeasureSpec){  super.onMeasure(wIDthMeasureSpec, heightmeasureSpec);  取得父VIEwGroup指定的宽高测量模式和尺寸  int wIDthSpecMode = MeasureSpec.getMode(wIDthMeasureSpec);  int wIDthSpecsize = MeasureSpec.getSize(wIDthMeasureSpec);  int heightSpecMode = MeasureSpec.getMode(heightmeasureSpec);  int heightSpecsize = MeasureSpec.getSize(heightmeasureSpec);  if (wIDthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {    如果宽高都是AT_MOST的话,即都是wrap_content布局模式,就用VIEw自己想要的宽高值        setMeasuredDimension(mWIDth, mHeight);  }else if (wIDthSpecMode ==如果只有宽度都是AT_MOST的话,即只有宽度是wrap_content布局模式,宽度就用VIEw自己想要的宽度值,高度就用父VIEwGroup指定的高度值if (heightSpecMode ==如果只有高度都是AT_MOST的话,即只有高度是wrap_content布局模式,高度就用VIEw自己想要的宽度值,宽度就用父VIEwGroup指定的高度值        setMeasuredDimension(wIDthSpecsize, mHeight);  }}

 在上面的代码中,我们要给 VIEw 指定一个默认的内部宽/高(mWIDth 和 mHeight),并在 wrap_content 时设置此宽/高即可。最后将在将宽高设置到 VIEw 上:

    // VIEw        protected voID setMeasuredDimension(int measureDWIDth,1)"> measuredHeight) {        boolean optical = isLayoutModeOptical(this);        if (optical != isLayoutModeOptical(mParent)) {            Insets insets = getopticalinsets();            int opticalWIDth  = insets.left + insets.right;            int opticalHeight = insets.top  + insets.bottom;             measureDWIDth  += optical ? opticalWIDth  : -opticalWIDth;            measuredHeight += optical ? opticalHeight : -opticalHeight;        }        setMeasuredDimensionRaw(measureDWIDth, measuredHeight);    }    voID setMeasuredDimensionRaw( measuredHeight) {        mMeasureDWIDth = measureDWIDth;        mMeasuredHeight = measuredHeight;        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;    }

这里就是把测量完的宽高值赋值给 mMeasureDWIDthmMeasuredHeight 这两个 VIEw 的属性,然后将标志位置为已测量状态。

子 VIEw 测量完成以后,会计算 childState,看下 combineMeasuredStates 方法 :

int combineMeasuredStates(int curState,1)"> newState) {        return curState | newState;    }

 当前 curState 为 0, newState 是调用 child.getMeasuredState() 方法得到的,来看下这个方法的具体逻辑:

      * Return only the state bits of {@link #getMeasureDWIDthAndState()}     * and { #getMeasuredHeightAndState()},combined into one integer.     * The wIDth component is in the regular bits { #MEASURED_STATE_MASK}     * and the height component is at the shifted bits     * { #MEASURED_HEIGHT_STATE_SHIFT}>>{ #MEASURED_STATE_MASK}.      getMeasuredState() {        return (mMeasureDWIDth&MEASURED_STATE_MASK)                | ((mMeasuredHeight>>MEASURED_HEIGHT_STATE_SHIFT)                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));    }

 该方法返回一个 int 值,该值同时包含宽度的 state 以及高度的 state 信息,不包含任何的尺寸信息。

MEASURED_STATE_MASK 的值为 0xff000000,其高字节的 8 位全部为 1,低字节的 24 位全部为 0。

MEASURED_HEIGHT_STATE_SHIFT 值为 16。

将 MEASURED_STATE_MASK 与 mMeasureDWIDth 做与 *** 作之后就取出了存储在宽度首字节中的 state 信息,过滤掉低位三个字节的尺寸信息。

由于 int 有四个字节,首字节已经存了宽度的 state 信息,那么高度的 state 信息就不能存在首位字节。MEASURED_STATE_MASK 向右移 16 位,变成了 0x0000ff00,这个值与高度值 mMeasuredHeight 做与 *** 作就取出了 mMeasuredHeight 第三个字节中的信息。而 mMeasuredHeight 的 state 信息是存在首字节中,所以也得对mMeasuredHeight 向右移相同的位置,这样就把 state 信息移到了第三个字节中。

最后,将得到的宽度 state 与高度 state 按位或 *** 作,这样就拼接成一个 int 值,该值首个字节存储宽度的 state 信息,第三个字节存储高度的 state 信息。

这些都得到之后,就可以开始去计算父 VIEw 的尺寸了:

        // 确定父 VIEw 的宽高        setMeasuredDimension(resolveSizeAndState(maxWIDth,1)">resolveSizeAndState(maxHeight,childState << MEASURED_HEIGHT_STATE_SHIFT));

 下面开始看 resolveSizeAndState 具体逻辑:

 VIEw 的静态方法int resolveSizeAndState(int measureSpec,1)"> childMeasuredState) {         MeasureSpec.getSize(measureSpec);         result;         (specMode) {             MeasureSpec.AT_MOST:                if (specsize < size) {                    result = specsize | MEASURED_STATE_TOO_SMALL;                }  {                    result = size;                }                 MeasureSpec.EXACTLY:                result = specsize;                :                result = size;        }        return result | (childMeasuredState & MEASURED_STATE_MASK);    }

这个方法的代码结构跟前文提到的 getDefaultSize()方法很相似,主要的区别在于 specMode 为 AT_MOST 的情况。我们当时说 getDefaultSize() 方法是没有适配wrap_content 这种情况,而这个 resolveSizeAndState() 方法是已经适配了 wrap_content 的布局方式,那具体怎么实现 AT_MOST 测量逻辑的呢?有两种情况:

当父 VIEwGroup 指定的最大尺寸比 VIEw 想要的尺寸还要小时,会给这个父 VIEwGroup  的指定的最大值 specsize 加入一个尺寸太小的标志  MEASURED_STATE_TOO_SMALL,然后将这个带有标志的尺寸返回,父 VIEwGroup 通过该标志就可以知道分配给 VIEw 的空间太小了,在窗口协商测量的时候会根据这个标志位来做窗口大小的决策。

当父 VIEwGroup 指定的最大尺寸比没有比 VIEw 想要的尺寸小时(相等或者 VIEw 想要的尺寸更小),直接取 VIEw 想要的尺寸,然后返回该尺寸。

getDefaultSize() 方法只是 onMeasure() 方法中获取最终尺寸的默认实现,其返回的信息比 resolveSizeAndState() 要少,那么什么时候才会调用 resolveSizeAndState() 方法呢? 主要有两种情况:

AndroID 中的大部分 VIEwGroup 类都调用了 resolveSizeAndState() 方法,比如 linearLayout 在测量过程中会调用 resolveSizeAndState() 方法而非 getDefaultSize()方法。

我们自己在实现自定义的 VIEw 或 VIEwGroup 时,我们可以重写 onMeasure() 方法,并在该方法内调用 resolveSizeAndState() 方法。

到此,终于把 VIEw 测量过程讲完了。

下一篇开始讲 VIEw 的 layout 和 draw 过程。

 

参考文章Android源码完全解析——View的Measure过程View的绘制流程  总结

以上是内存溢出为你收集整理的Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp全部内容,希望文章能够帮你解决Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImplAndroid View 的绘制流程之 Measure 过程详解 (一)Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)Android View 的事件分发原理解析Android 自定义 View 详解Android View 绘制流程之 DecorView 与 ViewRootImp所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1121332.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-29
下一篇 2022-05-29

发表评论

登录后才能评论

评论列表(0条)

保存