android view 原理 -- measure 分析与应用

来源:互联网 发布:按键精灵淘宝秒杀脚本 编辑:程序博客网 时间:2024/06/06 02:06

1 概述

measure方法,主要是用于测量android中view的大小,为后面的layout做好准备,这里我们主要来看measure的流程。

2 分析

查看view中的方法,

 public final void measure(int widthMeasureSpec, int heightMeasureSpec)

这个方法是测量方法,但是这里这个方法是final的,也就是说无法重写,其实这里面最终是调用onMeasure来完成测量。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

android的视图测量就是从上到下遍历的测量,他的流程如下:

这里写图片描述

比较简单,值得注意的是measure方法中有一个widthMeasureSpec值,这个值是一个int,它的高两位代表了specMode,低30位代表了specsize,可以使用MeasureSpec来打包和解包这个值,从而获得mode和size。

先来看看MeasureSpec的mode:

UNSPECIFIED//不做限制EXACTLY//指定精确值,在match_parent和具体数值时,适用与这种场景AT_MOST//指定最大值,往往对应于layoutParams中的wrap_content

一个普通view的mode如何来确立呢,我们查看android中viewGroup的源码中的getChildMeasureSpec方法,如下:

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 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;        }        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);    }

这里的代码其实比较简单,总的来说,从参数来看,需要的参数是当前viewGroup的MeasureSpec以及padding,还有子view的大小,在里面判断的时候,还会结合子view的layoutParams。这里可以把上面的代码总结成一个表格:

这里写图片描述

可以看到,parent和子view的关系。

3 应用

(1) 自定义控件实现wrap_content

在自定义控件的时候,如果不重写onMeasure方法,往往wrap_content配置不起作用,这是为什么呢。我们先来看看View的onMeasure方法:

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

这里面调用了setMeasuredDimension方法来设置最终的大小,所以这里面决定大小的实际上是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;    }

这里我们先不管参数size,在AT_MOST和EXACTLY这两种模式下用不到,我们结合前面讲解的getChildMeasureSpec方法以及表格可以知道,当前view的MeasureSpec是根据parent和当前view的layoutParams决定的,例如wrap_content,这个在上面的表格中有所体现,可以看到,最终wrap_content和match_parent和parent的mode结合后,就会产生AT_MOST和EXACTLY这两种模式,且size都是parent的size,而上面的方法中,这两种模式的处理方式都是直接返回specSize,由于size是parent的size,也就导致了其实wrap_content和match_parent一致。

所以我们需要重写onMeasure方法来实现wrap_content

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {            //宽高都是wrap_content            setMeasuredDimension(mWidth, mHeight);        } else if (widthSpecMode == MeasureSpec.AT_MOST) {            //宽是wrap_content            setMeasuredDimension(mWidth, heightMeasureSpec);        } else if (heightMeasureSpec == MeasureSpec.AT_MOST) {            //高是wrap_content            setMeasuredDimension(widthMeasureSpec, mHeight);        }    }

这里的mWidth和mHeight是自己根据情况自定的,以便实现wrap_content效果。

(2) 获取view宽高

要获取view的宽高,必须等待view测量完毕后才能得到正确的大小,那么如何保证获取的时候已经测量完毕,这里一共有四种方法

(a)onWindowFocusChanged

这个方法activity和view都有,在焦点发生变化的时候,就会回调这个方法。

    @Override    public void onWindowFocusChanged(boolean hasWindowFocus) {        super.onWindowFocusChanged(hasWindowFocus);        if (hasFocus()) {            //获取大小        }    }

(b)view.post(runnable)

我们知道,android中view的事件,包括touch,测量等等,都是通过发送消息到ui线程来执行的,那么使用这个方法,可以将我们获取大小的方法加入到队列末端。从而保证在测量完成后执行。

        post(new Runnable() {            @Override            public void run() {                //获取大小            }        })

(c)ViewTreeObserver

这个类可以是一个view树的观察者,当它内部的view发生变化的时候就会回调其中响应的方法,这里使用OnGlobalLayoutListener,当view的layout状态发生变化的时候,就会回调。由于layout是发生在onmeasure之后的,所以保证了测量的完成。由于这个方法可能多次回调,所以在回调中,需要注销监听。

        ViewTreeObserver observer = view.getViewTreeObserver();        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {            @Override            public void onGlobalLayout() {                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);                //获取大小            }        });

(d)view.measure(int widthMeasureSpec, int heightMeasureSpec)

这种方法是直接调用view的measure方法来手动测量,但是这里要根据不同的layoutParams来做不同的方法。

match_parent

这种情况没办法测量,因为这里并不知道parent的大小。

具体数值

指定了具体数值,那么根据前面的表格,可以看到,mode就是EXACTLY,所以可以使用如下代码:

        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);        view.measure(widthMeasureSpec,heightMeasureSpec);

这里的width和height就是指定的具体宽高。

wrap_content

同样的,根据前面的表格,这里的模式是AT_MOST,所以使用如下代码:

        int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);        int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1, MeasureSpec.AT_MOST);        view.measure(widthMeasureSpec,heightMeasureSpec);

前面也讲到了,这里的widthMeasureSpec高两位是mode,低30是大小。所以必须左移30后减1,从而得到最大的值。

这篇文章介绍了measure的流程,以及应用,就到这里。

0 1
原创粉丝点击