View绘制流程之measure过程

概述

View的绘制包括measure,layout,draw三个过程,即测量,布局,绘制。measure是对view的宽高进行测量,layout是确定view的四个顶点,即确定view的位置,draw是将最终的view绘制到屏幕上。

measure过程

如果只是一个单一的view,只需要对自身进行measure就足够了,但如果是一个viewgroup,除了对自身进行measure外还需要遍历它所有的子view,并调用其子view的measure,然后子view再执行以上递归操作。所以view的measure要分两个部分进行。

1.单一view的measure过程

	public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
     		//。。。
            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                //调用onMeasure进行具体的测量
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                
            }
    }
    
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

	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
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }

	private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
		//将最终测量的宽高进行保存
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }

可以看到,单一view的总体测量过程很简单,就是在onMeasure中测量后把宽高保存在mMeasuredWidth,mMeasuredHeight两个本地变量中。接下来看一下具体的测量过程。

//参数介绍
// size:默认大小  measureSpec:测量规格
public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        // 获取测量模式
        int specMode = MeasureSpec.getMode(measureSpec);
        // 获取测量大小
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //UNSPECIFIED 就使用默认大小
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
            // AT_MOST、EXACTLY就使用测量大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }


	protected int getSuggestedMinimumWidth() {
		//如果view设置了背景,就取背景的最小宽和view最小宽中的最大值
		//如果view没有设置背景,就取view的最小值
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
	/**
	*如果view设置了背景drawable,就返回drawable的原始宽度,否则就返回0
	*/
	public int getMinimumWidth() {
        final int intrinsicWidth = getIntrinsicWidth();
        return intrinsicWidth > 0 ? intrinsicWidth : 0;
    }

UNSPECIFIED:不约束,主要用于listView,scrollview等系统控件中,自定义view一般用不到。
AT_MOST:对应wrap_content,自适应大小。
EXACTLY:精确的,对应match_parent和具体数值。
重点看一下getDefaultSize,当测量模式为UNSPECIFIED时,就使用默认大小,即getSuggestedMinimumWidth中的逻辑,这个我们平时用不到,暂且不看。当测量模式为AT_MOST、EXACTLY时,就使用view的测量大小,可以看到view的宽高是由specSize决定的,如果我们自定义view时直接继承view,而不重写onMeasure()AT_MOST情况下的逻辑,最终view的大小会和match_parent情况一样充满整个父布局,相信很多同学刚开始学习自定义view时都遇到过wrap_content不起作用问题,现在我们找到了问题的关键,就可以对症下药,只需要在AT_MOST时为view设置一个默认值就可以了,对于这个默认大小就要视情况而定了,比如textview的默认大小是里面文字的宽度。
就像这样,当然我们一般不会这么写,而是会重写onMeasure方法,但是思路是一样的。

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        // 获取测量模式
        int specMode = MeasureSpec.getMode(measureSpec);
        // 获取测量大小
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //UNSPECIFIED 就使用默认大小
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
            // AT_MOST、EXACTLY就使用测量大小
        case MeasureSpec.AT_MOST:
        	result = 合适的默认大小;
        	break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

viewGroup的measure过程

viewGroup的measure过程除了对自己进行measure外,还需要对内部的子view进行measure,viewGroup是一个继承view的抽象类,它为我们提供了一个measureChildren()方法。

	protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

	protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureChildren()的作用是遍历所有子view,Visible设置为Gone的除外,并根据自己的MeasureSpec和子view的LayoutParams参数通过getChildMeasureSpec()方法确定子view的MeasureSpec,然后将MeasureSpec传递给子view的measure方法进行子view的测量。

为了更好理解viewGroup的measure过程,我们来看一下LinearLayout的onMeasure()方法,以VERTICAL情况为例。

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mOrientation == VERTICAL) {
            measureVertical(widthMeasureSpec, heightMeasureSpec);
        } else {
            measureHorizontal(widthMeasureSpec, heightMeasureSpec);
        }
    }

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
        
        // See how tall everyone is. Also remember max width.
        for (int i = 0; i < count; ++i) {
            final View child = getVirtualChildAt(i);
            
             measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);


                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
                       lp.bottomMargin + getNextLocationOffset(child));

   
            }
            
        // Add in our padding
        mTotalLength += mPaddingTop + mPaddingBottom;

        int heightSize = mTotalLength;

        // Check against our minimum height
        heightSize = Math.max(heightSize, getSuggestedMinimumHeight());

        // Reconcile our calculated size with the heightMeasureSpec
        int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
        heightSize = heightSizeAndState & MEASURED_SIZE_MASK;
        

        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                heightSizeAndState);
    }

上面是垂直方向measure的核心代码,宽高是match_parent或具体数值就不多说了,因为这种类型遵循view的宽高测量,主要来看自适应情况下高的处理,可以看到,整个流程大概分为三步,首先遍历所有的子view,获取他们的高度,包括子view的高度和子view垂直方向的margin,用mTotalLength这个变量来记录每次遍历的view的高度,每遍历一次,mTotalLength就增加一次,直到所有的view全部遍历。然后计算layout自己的高度,当然这个高度不能超过父布局的高度。最终调用setMeasuredDimension进行宽高保存。

总结

单一view只需测量自身就可以完成measure,而viewGroup需要依赖子view的宽高来确定自己的测量宽高,但在某些情况下,系统会多次measure来获得最终的测量宽高,所以在layout中获取view的测量宽高是最合适的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值