Gridview控件整体的高度设置

最近做需求需要用到gridview。发现这算得上是官方控件里比较不人性的一个了。为什么这么说呢,它的高度和它内部item的高度设置都不尽如人意。

Gridview控件整体的高度

我们知道gridview里边设置item,还可以设置列数。然后item个数超过列数就会折行,这样就显示成类似表格的样式。
写布局的时候没多想,直接把高度设置成了“wrap_content”。然后写adapter,传数据。运行。可是运行出来发现之显示了一行,第二行以后的item被无情的盖住了。
Gridview控件整体的高度设置_第1张图片
那么改成“match_parent”,依然不行。除非设成固定高度。
这怎么能忍。可是设置成固定高度又不现实,数据是动态的。
于是首先想到的方法自然是获取item个数,然后计算行数,用行数乘以每行的高度,然后通过layoutParam传给gridview。这自然是一种方案。但是太low了。不喜欢。
我们还是进一步看看GridView的原理,为什么会得出这样一个效果。
直接找到计算高度的onMeasure()方法,由于方法很长,我们直接截取计算高度的部分

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        ...
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                    getVerticalFadingEdgeLength() * 2;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            int ourSize =  mListPadding.top + mListPadding.bottom;

            final int numColumns = mNumColumns;
            for (int i = 0; i < count; i += numColumns) {
                ourSize += childHeight;
                if (i + numColumns < count) {
                    ourSize += mVerticalSpacing;
                }
                if (ourSize >= heightSize) {
                    ourSize = heightSize;
                    break;
                }
            }
            heightSize = ourSize;
        }
        ...
        ...
        setMeasuredDimension(widthSize, heightSize);
    }

先大概看一下,就是这个高度值有两种模式,如果是UNSPECIFIED,那么就只有一行的高度,加上上下边距和效果。如果是AT_MOST,那么高度就是所有行的高度,加上上下边距。
显然我们之前显示的效果就是UNSPECIFIED模式的效果。那么我们看看这个到底是什么意思。
这里有一篇文章,讲的比较清楚。
MeasureSpec是一个int值。转换成2进制一共有32位。这32位中,低30位表示高度或宽度的值,而高2位则代表高度或宽度的模式,
一共有三种模式:
1. UNSPECIFIED = 0 << MODE_SHIFT; 即: 00000000 00000000 00000000 00000000 父容器不对子View有任何限制
2. EXACTLY = 1 << MODE_SHIFT; 即: 01000000 00000000 00000000 00000000 父容器已经测量出子View所需要的大小,即measureSpec中封装的specsize
3. AT_MOST = 2 << MODE_SHIFT; 即: 10000000 00000000 00000000 00000000 父窗口限定了一个最大值给子View即specsize

而这三种模式的设置又同时和父VIew和自身设置有关,这张图比较直观:

我们的情况应该是gridview的父view是UNSPECIFIED,所以除非设置具体高度,否则自身的模式都是UNSPECIFIED。为了验证这一点,我自定义了一个View继承GridView并重写了onMeasure方法,分别把高度设置成“wrap_content”和“match_paretn”,输出heightMeasureSpec,结果不出意外,都是0。所以高2位也是00,即UNSPECIFIED的情况。

但是我们也不能随意修改父view,不过既然我们已经重写了onMeasure方法,完全可以自己修改heightMeasureSpec,把他的模式强制改成AT_MOST,代码如下

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int heightSpec;
        LOGGER.d("centerAAA", "heightMeasureSpec = " + Integer.toBinaryString(heightMeasureSpec));
        if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
            heightSpec = MeasureSpec.makeMeasureSpec(
                    0x3fffffff, MeasureSpec.AT_MOST);
        }
        else {
            heightSpec = heightMeasureSpec;
        }
        super.onMeasure(widthMeasureSpec, heightSpec);
    }

这里只修改我们wrap_content的情况,避免影响其他设置。
我们通过MeasureSpec.makeMeasureSpec(0x3fffffff, MeasureSpec.AT_MOST);这个方法修改的。
我们看下这个方法的实现:

public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

这个判断是判断的版本,sdk低于17的老版本就用笨方法,直接加,但是这样性能不够好。在17之后采用了与或运算
MODE_SHIFT = 30;
MODE_MASK = 0x3 << MODE_SHIFT;
也就是说,首先通过与运算吧size的高2为置为0,再把mode的低30为置为0,然后两者执行或运算,直接得到结果。
总之最后就是把表示模式的放到高2位,把值放到低30位,拼成一个int值返回。

所以第二个参数我们手动把它设置成AT_MOST。至于第一个参数。我们看到AT_MOST的意思是限制子view的最大值,在这个范围内都能包住。所以我们尽量设置一个最大值,也就是低30位都为1。这样就能成功显示了:

你可能感兴趣的:(Android)