View的绘制流程(1) - Measure
标签搜索

View的绘制流程(1) - Measure

Pop.Kite
2022-12-10 / 2 评论 / 150 阅读 / 正在检测是否收录...
View的绘制流程

view的绘制.png

概念

  • View:View是所有UI组件的基类
  • ViewGroup:UI组件的容器,本身也是View的子类
  • ViewRootImpl:绘制View的辅助类
  • MeasureSpec:描述View相关信息的一种属性

MeasureSpec

measure方法中,系统会根据父容器约束的信息将当前view的LayoutParams封装成measureSpec,然后View的onMeasure方法根据measureSpec值来计算当前View的宽高。

顶层的DecorView调用getRootMeasureSpec获取屏幕的宽和高作为MeasureSpec属性

MeasureSpec是一个int类型的属性,高2位表示SpecMode(模式),低30位表示SpecSize(尺寸)。(详见View.java中的静态类MeasureSpec)

  • MeasureSpec提供封装和解包的方法

    封装:makeMeasureSpec()

    解包:getMode() \ getSize()

  • MeasureSpec的模式

    • AT_MOST:view的大小需要计算,对应于 wrap_content
    • EXACTLY:父容器已经得知view的大小,对应于 match_parent 或者指定大小
    • UNSPECIFIED:view的大小不受父容器的限制,随意大小

View的测量

流程

viewMeasure.png

DecorView的测量

performTraversals

view的测量是从根节点View(此处我们假设是DecorView)开始逐层递归进行的。

/**
* ViewRootImpl.java
**/
private void performTraversals() {
  ...
  //获取视窗大小
  int childWidthMeasureSpec = getRootMeasureSpec (mWidth, lp.width);
  int childHeightMeasureSpec = getRootMeasureSpec (mHeight, lp.height);
  //测量视图大小,从DecorView开始
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
}

/**
* 测量视图
**/
private void performMeasure(int childWidthMeasureSpec,
                            int childHeightMeasureSpec) {
  if (mView == null) {
    return;
  }
  ...
  //调用View.java 的measure方法
  mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  ...
}

measure

View的measure方法及逐级调用:

//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
 
  ...
    
  //如果含有PFLAG_FORCE_LAYOUT会强制刷新布局,reqeustLayout会加入此标记
  final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;    
  
  ...
    
  //判断是否需要重新测量,如果宽高没有发生变化,则不需重新
  if (forceLayout || needsLayout) {
    //清除表示已经测量过的标记位
    mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    //从右向左解析布局,如果其他布局需要特殊处理
    resolveRtlPropertiesIfNeeded();
    //从缓存中读取信息
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
      //缓存不可用,从此处开始测量视图尺寸,调用onMeasure方法
      onMeasure(widthMeasureSpec, heightMeasureSpec);
      mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } else {
      //缓存可用,从缓存中取值,不必重新测量
      long value = mMeasureCache.valueAt(cacheIndex);
      //直接保存尺寸信息
      setMeasuredDimensionRaw((int) (value >> 32), (int) value);
      mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    
    // 自定义View没有调用setMeasuredDimension方法保存测量结果,会抛出异常
    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()");
        }
        mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
    }
  
  ...
    
}

//测量尺寸并保存
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),
                                      widthMeasureSpec),
                       getDefaultSize(getSuggestedMinimumHeight(),
                                      heightMeasureSpec));
}


//如果View没有背景,返回View本身的最小宽度,如果设置了背景,取二者较大值
protected int getSuggestedMinimumHeight() {
  return 
    (mBackground == null) ? mMinHeight : max(mMinHeight,
                                             mBackground.getMinimumHeight());    }

//
public static int getDefaultSize(int size, int measureSpec) {
  //view意图的尺寸
  int result = size;
  //获取SpecMode和SpecSize
  int specMode = MeasureSpec.getMode(measureSpec);
  int specSize = MeasureSpec.getSize(measureSpec);
  
  switch (specMode) {
    //该模式不限制view尺寸,view尺寸可以随心所欲
    case MeasureSpec.UNSPECIFIED:
      result = size;
      break;
    //该两个模式下,view尺寸由父容器指定
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
      result = specSize;
      break;
  }
  return result;
}

//保存尺寸信息
protected final void setMeasuredDimension(int measuredWidth, 
                                          intmeasuredHeight) {
  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);
}

//保存尺寸
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
  //保存测量完的尺寸信息到view的成员变量中
  mMeasuredWidth = measuredWidth;
  mMeasuredHeight = measuredHeight;
  
  //设置标识位为测量完毕状态
  mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

onMeasure

需要注意,DecoreView的是FrameLayout的子类,因此meaure方法调用的是FramenLayoutonMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
      //子View数量
    int count = getChildCount ();

      //宽或高是否匹配match_parent模式
    final boolean measureMatchParentChildren =
        MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
              //逐个遍历可见的View
        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相同宽或高的view
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                    lp.height == LayoutParams.MATCH_PARENT
                ) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }

    //该View的宽高需要加上父View的padding
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }

      //保存测量结果
    setMeasuredDimension(
        resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
        resolveSizeAndState(
            maxHeight, heightMeasureSpec,
            childState < < MEASURED_HEIGHT_STATE_SHIFT
        )
    );

      //获取match_parent的view数量
    count = mMatchParentChildren.size();
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child . getLayoutParams ();

              //遍历算的view的宽高
            final int childWidthMeasureSpec;
            if (lp.width == LayoutParams.MATCH_PARENT) {
                  //view的宽是父view的宽 - 父padding - 子margin
                final int width = Math.max(
                    0, getMeasuredWidth()
                            - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                            - lp.leftMargin - lp.rightMargin
                );
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                    width, MeasureSpec.EXACTLY
                );
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(
                    widthMeasureSpec,
                    getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                            lp.leftMargin + lp.rightMargin,
                    lp.width
                );
            }

            final int childHeightMeasureSpec;
            if (lp.height == LayoutParams.MATCH_PARENT) {
                final int height = Math.max(
                    0, getMeasuredHeight()
                            - getPaddingTopWithForeground() - getPaddingBottomWithForeground()
                            - lp.topMargin - lp.bottomMargin
                );
                childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
                    height, MeasureSpec.EXACTLY
                );
            } else {
                childHeightMeasureSpec = getChildMeasureSpec(
                    heightMeasureSpec,
                    getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                            lp.topMargin + lp.bottomMargin,
                    lp.height
                );
            }
          
              //重新测量
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

measureChildWithMargins

measureChildWithMargins方法:

parentWidthMeasureSpec和parentHeightMeasureSpec是父view对于子view的约束条件,即子view可获得的最大宽度和高度。widthUsed和heightUsed描述父view已经使用的宽度和高度。
protected void measureChildWithMargins(View child,
                                       int parentWidthMeasureSpec,
                                       int widthUsed,
                                       int parentHeightMeasureSpec,
                                       int heightUsed) {
  
  //获取布局参数
  final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
  //通过父View的WidthMeasureSpec计算子view的WidthMeasureSpec
  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大小
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

getChildMeasureSpec

测量子view的时候需要考虑子view的尺寸以及margin值还有padding值,这些参数通过调用getChildMeasureSpec获得,这个方法把父view的measureSpec以及自身的layoutParams属性传递进去获取子view的measureSpec。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        case 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 = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let them have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

View的测量

measure

子view的measure方法参考 @DecorView的measure方法

onMeasure

不同的View对应的onMeasure方法的实现有所差异,略。

https://www.wangt.cc/2021/01/深入理解android中view绘制三大流程及measurespec详解/

1

评论 (2)

取消
  1. 头像
    Leon
    Linux · Google Chrome

    6666

    回复
    1. 头像
      popkter 作者
      MacOS · Safari
      @ Leon

      画图

      回复