Android透过源码了解View的measure过程

导语

measure是控件工作流程中的测量,是Android很重要的一块内容,他分为对View及ViewGroup的测量,两种测量方式是不一样的,下面通过源码分析View的measure过程。

View的measure过程

View的measure过程是由measure方法来完成,不过该方法是用final关键字修饰的,所以子类不能继承,可以用该方法中的onMeasure方法完成对view的测量工作。

	public final void measure(int widthMeasureSpec, int heightMeasureSpec){
		...
		onMeasure(widthMeasureSpec, heightMeasureSpec);
		...
	}

看onMeasure方法源码

	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(
        	getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
			getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
	}

代码很简洁,其中setMeasuredDimension方法设置View 宽/高的测量值,因此只要看getDefaultSize方法即可。

	public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

MeasureSpec是什么?
它是一个32位的int值,高2位代表specMode,就是通过MeasureSpec.getMode获取,低30位代表specSize 通过MeasureSpec.getSize方法获取,

MeasureSpec如何形成的?
在测量过程中会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec。

specMode有三类:
UNSPECIFIED
父容器不对View有任何限制,一般用于系统内部。
AT_MOST
父容器指定一个可用大小即SpecSize,View的大小不能大于这个值。它对应Layoutparams的wrap_content。
EXACTLY
父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值。它对应LayoutParams中的match_parent和具体数值这两种模式。

在这里插入图片描述

继续回到源码分析,我们可以看到getDefaultSize返回的大小就是specSize即View测量后大小,这里多次提及测量后大小,是因为View最终大小是在layout阶段确定的,但是几乎所有情况下view测量大小和最终大小是相等的,至于UNSPECIFIED对应的getSuggestedMinimumWidth()这种情况一般用于系统内部测量过程,暂时不做研究。

从getDefaultSize来看,View的宽高是由specSize决定的,但是我们发现在布局中使用wrap_content(对应specMode的AT_MOST)和match_parent(对应specMode的EXACTLY)是等价的,都等于specSize,查View的MeasureSpace创建规则表发现,specSize就是parentSize,而parentSize是父容器中目前可使用的大小。显然在布局中宽/高设置wrap_content系统默认测量是不准确的,那么怎么解决这个问题呢?

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int mWidth = 100;
        int mHeight = 100;

        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSpecSize = MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecSize = MeasureSpec.getMode(heightMeasureSpec);

        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth, mHeight);
        } else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(mWidth, heightSpecSize);
        } else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, mHeight);
        }
    }

在自定义view控件的时候,当specMode检测到时wrap_content的时候设置默认测量值。

ViewGroup的measure过程

对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是ViewGroup是个抽象类,因此它没有重写View的onMeasure方法,但是提供了一个measureChildren的方法。

原理很简单,有兴趣的同学可以自行看源码了解。

有一种情况,在Activity启动的时候就想知道View的宽/高,有的同学可能觉得很简单,在onCreate或者onResumn中去获取View的宽高。实际上在onCreate,onResumn和onStart中均无法得到View的宽高信息,这是因为对View的测量过程和Activity的生命周期是不同步的。那么怎么在界面启动的时候拿到View的测量宽高值呢?

Activity启动时获取View的测量宽高

(1)通过onWindowFocusChanged获取,这个方法的意思是,view加载完了,可以拿取测量宽高了。

	@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if(hasFocus){
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
        }
    }

(2)通过post,将runnable投递到消息队列尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。

	@Override
    protected void onStart() {
        super.onStart();
        view.post(new Runnable() {
            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }

(3)当View树内部发生可见性改变时,addOnGlobalLayoutListener回调会被调用。

	@Override
    protected void onStart() {
        super.onStart();
        ViewTreeObserver observe = view.getViewTreeObserver();
        observe.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
    }
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值