View的measure过程

问题概述

一个View想要展示到界面上要经过三个流程:

  1. measure过程,也就是确定View的长和宽
  2. layout过程,确定View的位置
  3. draw过程,将View绘制到界面上

本文首先介绍measure流程,measure从其字面意思就是测量的意思.也就是获取View的尺寸.
安卓的测量分成两种,一种是View;另一种是ViewGroup。ViewGroup可以认为是布局,其实安卓中的五大布局就是继承了这个类,View就是一般的控件,里面不包含其他View.
因此可以把测量的过程看成从ViewGroup开始,遍历其中的所有的子View(包含ViewGroup),对其一一进行测量.

ViewGroup的测量过程

先大概看一下ViewGroup测量的调用流程:
measure–>onMeasure–>measureChildren–>measureChild–>getChildMeasureSpec–>child.measure
首先要注意ViewGroup其实是View的一个子类,measure和onMeasure方法是在View里面实现的,ViewGroup中并没有重写;另外一般自定义ViewGroup时重写onMeasure和onLayout方法,因此调用流程会和上面不一样,但是基本思路是一样,而且在重写的onMeasure方法中一般都会用到getChildMeasureSpec这个方法,所以先看一下默认的ViewGroup测量过程:

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);
            }
        }
    }

上面一段代码的大意就是对每一个子View进行测量。

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();
		//获取child的横向尺寸,把相关的padding值传进去,padding值代表父View的内间距
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
		//调取子View的具体测量尺寸
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

首先对上面的”获取child的横向尺寸“意思是获取child的”可能尺寸“,也就是一种测量,不是最后确认的尺寸,是根据父View的大小和限制条件得出子View可能的尺寸。
measure得出的是一个int整数,高两位代表测量模式,低30位代表测量的尺寸.MeasureSpec这个类对获取尺寸和测量模式方法进行了封装:

public static int getMode(int measureSpec) {
       return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
       return (measureSpec & ~MODE_MASK);
}

接下来就要通过getChildMeasureSpec这个方法获取子View的测量
getChildMeasureSpec这个方法很重要,理解其代码对安卓布局有很大的意义,代码如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
		//获取测量的模式和尺寸
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
		//padding代表父View的内间距,因此子View的测量要再次基础上
        int size = Math.max(0, specSize - padding);
		//保存根据计算得到的最后的测量模式和尺寸
        int resultSize = 0;
        int resultMode = 0;
		//子View的测量结果和父View的测量模式和尺寸都有关系
        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 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 = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

上面代码的基本意思就是根据父View的测量模式和子view的测量模式得出子View的宽高,其对应关系如下表:
来自安卓艺术开发探索
因此ViewGroup的测量过程就是首先获取所有子View的测量,然后实现ViewGroup自身的测量过程。

View的测量过程

对于View来说,其measure过程是measure—>onMeasure–>setMeasuredDimension
其中setMeasuredDimension在View中的实现过程就是简单的将传入的参数赋值给View的成员变量,也就是确定了大小.
一般在自定义View时,只需要重写View的onMeasure方法,加上自己定义的measure过程,最后调用setMeasuredDimension即可.

自定义实现View

首先要明确在自定义View的时候要针对wrap_content的情况做特殊处理,因为在上面的表格中,子View为match_parent和wrap_parent时候的size都是一样的,只是测量模式不同,这样的话,不做处理的话会和match_parent属性造成一样的结果.看了安卓源码的TextView或者其他的View都对wrap_parent做了特殊处理,处理的思路是这样的,父View给了子View一个测量,这个测量模式是AT_MOST,大小为size,父View给了多大并不代表子View就要用这么大,只要不超过父View给定的尺寸就行,因此子View会先获取内容的大小contentSize,然后和size比较,如果contentSize小于或等于size,则把子View的尺寸定为contentSize,按照这样的思路我们实现一个自定义的View.
当自定义View为wrap_content属性时,我们定义一个默认大小DEFAULT_SIZE,使得View的尺寸为DEFAULT_SIZE。
代码如下:

public class MyView extends View {
	private int DEFAULT_SIZE=100;
	public MyView(Context context) {
		super(context);
	}

	public MyView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public MyView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		setMeasuredDimension(measureWidth(widthMeasureSpec),
				measureHeight(heightMeasureSpec));
	}

	private int measureWidth(int widthMeasureSpec) {
		return widthMeasureSpec;
	}

	private int measureHeight(int heightMeasureSpec) {
		int resultSize = 0;
		int specMode = MeasureSpec.getMode(heightMeasureSpec);
		int specSize = MeasureSpec.getSize(heightMeasureSpec);
		if (specMode == MeasureSpec.EXACTLY) {
			resultSize = specSize;
		} else if (specMode == MeasureSpec.AT_MOST) {
		//把当前View的尺寸定为DEFAULT_SIZE和父View限制尺寸的最小值
			resultSize = DEFAULT_SIZE;
			resultSize = Math.min(resultSize, specSize);
		}
		return resultSize;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值