重温View测量之MeasureSpec

需求

 



这个是手机QQ吃喝玩乐里面的,选择城市界面


这个是手机QQ吃喝玩乐里面的,选择城市界面。就是一个ListView里面嵌套着不同规格的GridView/ListView,在比如电商里面的,物品分类界面,这种需求很常见,当然解决的办法也有很多。下面根据自己的工作经验介绍种很常用的方法。

 

理解MeasureSpec

 



以前刚刚接触android的时候,就感觉这还不简单,直接,在ListView的适配器中,根据gridview的行数乘以gridView的每个item,然后去设置gridView的LayoutParams,这样做不是不行,而是太浪费性能了,我们知道,在Adapter的getView方法里面不能做太多的工作,要做工作也要异步去完成,比如图片的加载。否则会出现卡顿,我们这里第一步要计算,第二部要去设置LayoutParams,在重新绘制,GridView,然而我们之前已经绘制GridView过一次了。可想而知,这不是一种很好的方法。

 

在介绍常规的方法之前,我们先复习下MeasureSpec,这是个很重要的知识点。因为android的View在测量的时候离不开他,也可以说,是根据他来测量View的大小的。MeasureSpec是一个32位的int值,最高的两位代表了SpecMode(测量模式),后面的低30位代表了SpecSize(规格大小)。SpecMode有三个值,分别是UNSPECIFIED、EXACTLY、AT_MOST。

   /**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.MATCH_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }

这是从ViewRootImpl(顶级View)中截取的代码段,根据布局的参数生成了我们所需要的MeasureSpec。如果我们的顶级View设置成了match_parent,那么我们只能给当前窗口的大小了,如果不是match_parent和wrap_content的话,我们只能给他所需要的大小了,这些都属于精确测量,比如50dp的大小。而如果顶级View是wrap_content模式的话,我们只能用最大模式At_most了,告诉下层的View,最大的空间也只有windowsize这么大了,你看着搞把。这样,我们在绘制子View的时候就会根据父View的MeasureSpec和自身的LayoutParams来测量和绘制自身了。


    /**
     * Does the hard part of measureChildren: figuring out the MeasureSpec to
     * pass to a particular child. This method figures out the right MeasureSpec
     * for one dimension (height or width) of one child view.
     *
     * The goal is to combine information from our MeasureSpec with the
     * LayoutParams of the child to get the best possible results. For example,
     * if the this view knows its size (because its MeasureSpec has a mode of
     * EXACTLY), and the child has indicated in its LayoutParams that it wants
     * to be the same size as the parent, the parent should ask the child to
     * layout given an exact size.
     *
     * @param spec The requirements for this view
     * @param padding The padding of this view for the current dimension and
     *        margins, if applicable
     * @param childDimension How big the child wants to be in the current
     *        dimension
     * @return a MeasureSpec integer for the child
     */
    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在绘制子View时,获取子View的MeasureSpec的代码,可以看到,从父View得到的不同的测量模式分别为精确测量,最大化测量和不准确测量,都有不同的结果。


实现

 



按照上面的源码分析,我们可以尝试把我之前的工作简单化处理,像计算和测量都让他绘制的时候自己去处理,这样效率会好很多。那么我们重新继承GridView,重写它的onMeasure方法:

 

public class MyGridView extends GridView{

    public MyGridView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
    }

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
        super.onMeasure(widthMeasureSpec, expandSpec);
    }

}

每次测量高度的时候,将GridView的高度改成AT_MOST模式测量,那么根据上面的分析,如果parent是AT_MOST,而子控件的高度是准确的话,那么子控件的规格就是 SpecMode :EXACTLY,SpecSize:childSize。

   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;

这样我们就完美的解决了我们的需求,在也不用自己测量,然后重新绘制了…


代码

 



仿QQ吃喝玩乐选择城市列表

 

 

转载于:https://www.cnblogs.com/Nipuream/p/5510048.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值