自定义ViewGroup之onMeasure套路及细节源码分析

1.ViewGroup的绘制流程

众所周知,ViewGroup的绘制流程分为onMeasure->onLayout->onDraw三个环节。这篇文章我们先来介绍一下onMeasure测量流程。

2.OnMeasure测量套路

  • 遍历ChildView
  • 对每一个ChildView进行测量
  • 获取每一个childVIew测量结果,从而根据开发者自身需求,计算ViewGroup整体宽高
  • 调用setMeasuredDimension(int measuredWidth, int measuredHeight)设置VIewGroup的尺寸

1. 遍历childView,相信应该不用过多解释了,请参考以下代码:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   //获取直接childView的个数  
   int count = getChildCount();
   for(int i = 0; i < count; i++){
       //对每一个ChildView进行测量
         ...
   }
}

 2.对每一个ChildView进行测量,这里我们需要知道childView的大小是由哪些条件决定的(划重点),这里先卖个关子,等看完源码后再来总结。

大家在测量childView的时候,多数会用到以下2个方法:

//遍历后对单个childView进行测量
1. measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

//直接对所有的childView进行测量,无须先遍历
2. measureChildren(int widthMeasureSpec, int heightMeasureSpec)

这两个方法最终都会通过传入的widthMeasureSpec和heightMeasureSpec来测量childView。那么这2个值从何而来呢?其实是当前ViewGroup的父View在自己的onMeasure方法中调用measureChild或measureChildren的时候传过来的,同理当前的ViewGroup也会在自己的onMeasure方法中调用measureChild或measureChildren将计算好的widthMeasureSpec和heightMeasureSpec传入childView的onMeasure中。

这里插入一个关于MeasureSpec的解释:

MeasureSpec是View的一个内部类。由32位的int数组成,高2位SpecMode,分为EXACTLY,AT_MOST和UNSPECIFIED,低30位SpecSize。下面是一张关于MeasureSpec创建规则的图片(重要)

继续回到childView的测量流程上面来,调用measureChild和measureChildren方法,最终都会通过getChildMeasureSpec方法获取childView对应的widthMeasureSpec和heightMeasureSpec 。

protected void measureChild(View child, int parentWidthMeasureSpec,
         int parentHeightMeasureSpec) {
    
    //获取childView的LayoutParams
    final LayoutParams lp = child.getLayoutParams();
    
    //根据ViewGroup传入的parentWidthMeasureSpec,ViewGroup水平方向的padding, child的LayoutParams的width,来获取childView的widthMeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
    //同上
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
    
    //根据getChildMeasureSpec获取的widthMeasureSpec和heightMeasureSpec进行测量childView
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

进入getChildMeasureSpec方法一探究竟。

    /**
     * 根据父View(ViewGroup)传入的MeasureSpec,父View(ViewGroup)的padding,以及childView自 
     * 身的size来获取childView自身的测量值。
     * @param 父View(ViewGroup)传入的MeasureSpec
     * @param 父View(ViewGroup)的padding
     * @param childView的LayoutParams得到的width或height,也有可能会在宽高基础上加上margin的 
     * 值
     * @return a MeasureSpec integer for the child
     */
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        //获取父View(ViewGroup)传入的MeasureSpec的SpecMode和SpecSize,即父View可以提供的最大 
        //尺寸,规格
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //表示childView可用的最大size,即父View最大尺寸-父View的padding
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        //根据父View传入的SpecMode,分为以下 3 * 3 种情况
        switch (specMode) {
        // 父View的大小是确定的
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {   //childView大小固定
                
                //childMeasureSpecSize = childView的LayoutParams中得到的尺寸
                resultSize = childDimension;  
                
                //childMeasureSpecMode = MeasureSpec.EXACTLY
                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.MATCH_PARENT) {  //MATCH_PARENT 
                
                //childMeasureSpecSize = size,即父View可以提供的最大size
                resultSize = size;
                
                //childMeasureSpecMode = MeasureSpec.EXACTLY
                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.WRAP_CONTENT) { //WRAP_CONTENT
                
                //childMeasureSpecSize = size,表示父View最大只能提供这么大的size了
                //注:最终childView的size会在自己的onMeasure中自行计算出实际值,这里是父View                        
                //给的最大参考值
                resultSize = size;
                
                //childMeasureSpecMode = MeasureSpec.AT_MOST
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // 父View也不知道自己有多大
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {  //childView大小固定

                //childMeasureSpecSize = childView的LayoutParams中得到的尺寸
                resultSize = childDimension;
                
                //childMeasureSpecMode = MeasureSpec.EXACTLY 表示大小确切
                resultMode = MeasureSpec.EXACTLY;

            } else if (childDimension == LayoutParams.MATCH_PARENT) { //MATCH_PARENT
                
                // childView与父View的尺寸一致,但是由于父View自己也不知道有多大,所以只能将
                // 自己最大的size提供给childView的MeasureSpecSize
                resultSize = size;
                
                //因为父View的大小不固定,所以childView的大小同样也不固定,所                
                //以childMeasureSpecMode = MeasureSpec.EXACTLY
                resultMode = MeasureSpec.AT_MOST;

            } else if (childDimension == LayoutParams.WRAP_CONTENT) {  //WRAP_CONTENT
                //childView自适应大小,父View最大只能提供size的大小了
                resultSize = size;
                //因为父View和childView自身均不确定大小,所以childMeasureSpec = 
                //MeasureSpec.AT_MOST
                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 him 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;
        }
        //将resultSize和resultMode组装成MeasureSpec对象,也就是childView的MeasureSpec了
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

 看到这里,不难理解,其实childView的大小由自身的LayoutParams和父View的MeasureSpec共同决定的,在childView获取到childMeasureSpecWidth和childMeasureSpecHeight之后,会调用child.measure(int widthMeasureSpec, int heightMeasureSpec)进行childView真正的测量,这时候会调用childView自身的onMeasure方法。在childView的onMeasure方法的最后都会调用setMeasuredDimension方法设置真正的size。来看一下setMeasuredDimension源码:

protected final void setMeasuredDimension(int measuredWidth, int 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);
}

进入setMeasuredDemensionRaw方法:

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    
    //将childView的onMeasure中最终计算得出的measureWidth值赋值给mMeasuredWidth保存
    mMeasuredWidth = measuredWidth;
    
    //将childView的onMeasure中最终计算得出的measureHeight值赋值给mMeasuredHeight保存
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

 这里会把childView最终测量的值赋值给mMeasureWidth和mMeasureHeight保存,其实childView.getMeasuredWidth返回的值就是mMeasureWidth,childView.getMeasureHeight返回的值就是mMeasureHeight。4.

3. 此时所有childView均已测量过了,调用getMeasureWidth和getMeasureHeight获取到各个childView的实际size后,根据业务需求将父view(ViewGroup)的size计算出来。

4. 在ViewGroup的onMeasure的最后,调用setMeasuredDimension将计算后的measureWidth和measureHeight传入,设置VIewGroup自身的Size。

3.总结

  • 在onMeasure中,view的大小与自身的LayoutParams和父View的MeasureSpec共同决定
  • onMeasure最后要调用setMeasuredDimension方法

至此,ViewGroup的onMeasure就结束啦。

 

 

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页