Android好奇宝宝_03_有点坑的GridView

第3篇,一个赞和评论都没有。。。让我一个人先哭会,我想静静,也别问我静静是谁。

好了,近期工作没事做在eoe论坛技术问答逛了逛,发现挺多关于GridView的问题。于是就看了下GridView的源码,一看发现有些地方还挺坑的。

问题:

(1)GridView显示高度不同的item时会出现错乱。

(2)GridView的position为0的getView会被调用N次。


开始分析:

问题有两个,但都是因为同个原因,Start read the fucking code!

个人意见:阅读所有关于View的源码时,从三个方法开始看:onMeasure(), onLayout(),onDraw()。

因为所有View的绘制流程都是:测量大小->位置布局->画。分别对应这三个方法,而且View类中这三个方法都是空实现。这就是在暗示如果你要继承View来产生一个新View,你应该去实现这三个方法,并且做相应的事情。所以一个自定义View的主要工作其实都是实现这三个方法(把ListView和GridView等当成谷歌出品的自定义View),所以我们当然要从最主要的开始看起。就像看一个女的都是:脸->胸->腿。

好了,废话那么多。就是想说明我是从这三个方法入手的。

在另一篇博文里我讲过ListView的onMeasure()方法,跟GridView很类似,有兴趣的可以去看下:传送门


GridView的onMeasure()方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // Sets up mListPadding
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMode == MeasureSpec.UNSPECIFIED) {
            if (mColumnWidth > 0) {
                widthSize = mColumnWidth + mListPadding.left + mListPadding.right;
            } else {
                widthSize = mListPadding.left + mListPadding.right;
            }
            widthSize += getVerticalScrollbarWidth();
        }
        
        int childWidth = widthSize - mListPadding.left - mListPadding.right;
        determineColumns(childWidth);
        //上面的是测量宽度的,不鸟
        //下面的是算出一个子View的高度,其实就是一个item的高度
        int childHeight = 0;

        mItemCount = mAdapter == null ? 0 : mAdapter.getCount();
        final int count = mItemCount;
        if (count > 0) {
        	//这里调用 obtainView(0, mIsScrap)来获得position为0的item view
            final View child = obtainView(0, mIsScrap);

            AbsListView.LayoutParams p = (AbsListView.LayoutParams)child.getLayoutParams();
            if (p == null) {
                p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT, 0);
                child.setLayoutParams(p);
            }
            p.viewType = mAdapter.getItemViewType(0);
            p.forceAdd = true;

            int childHeightSpec = getChildMeasureSpec(
                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 0, p.height);
            int childWidthSpec = getChildMeasureSpec(
                    MeasureSpec.makeMeasureSpec(mColumnWidth, MeasureSpec.EXACTLY), 0, p.width);
            //这里给一个规格让position为0的item(即这里的child)去测量一下自己,得到宽和高
            //tips:这种方法在我们自己自定义View时也可以用到,可以在view绘制出来之前来获得view的大小
            child.measure(childWidthSpec, childHeightSpec);
            //记录下得到的高,下面有用
            childHeight = child.getMeasuredHeight();

            if (mRecycler.shouldRecycleViewType(p.viewType)) {
                mRecycler.addScrapView(child);
            }
        }
        //这就是GridView嵌套在其它可以滚动的View(ListView,ScrollView)时,会只显示第一行的原因,详细点击上面得传送门
        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;
            //这里就是GridView为自己计算高度的地方
            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);
        mWidthMeasureSpec = widthMeasureSpec;
    }


阅读发现GridView计算自身高度的方程为:

Hight=行数*childHeight+(行数-1)*垂直间隔。

childHeight就是前面计算出来的第一个item的View的高度。

坑爹呢这是!

这样回到我们的第一个问题,如果我们的item高度不一样,因为GridView计算时全部都是按第一个来计算的,最后高度就会跟我们想要的不一样,布局就会出现问题,我们的item就会被上挤下压出现错乱。

至于第二个问题,GridView获得第一个item的view是调用的obtainView(0, mIsScrap)方法。obtainView()方法其实就是调用我们的Adapter的getView()方法来获得item的View的,请看码:

View obtainView(int position, boolean[] isScrap) {
		// 。。略。。。
		child = mAdapter.getView(position, scrapView, this);
		// 。。略。。。
		return child;
	}


所以每次GridView进行测量时,position为0的getView都会被调用。而GridView很容易被请求重新布局,子view的setBackGround,ImageView的设置图片等,只要是大小可能发生变化的,就会调用requestLayout()方法,该方法会层层向上请求进行重新布局。

如果你写一个GridView用来显示图片,并且用了ImageLoader来进行图片加载的话,你每次显示一张图片时,position为0的getView就会被调用一次。因为ImageLoader是先设置一张默认图片给ImageView,等图片下载完之后再设置给ImageView。


解决方法:

(1)第一个问题,如果你是想用来显示高度不同的图片,可以用瀑布流。

(2)第二个问题,可以加一些判断来提高一点效率(表示我也不知道有没有用,如果有好的建议跪求评论留言)

        boolean isFristGetZero = true;
	View zeroView;
	BaseAdapter mAdapter = new BaseAdapter() {

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			// TODO Auto-generated method stub
			Log.e("getView", "" + position);
			//position为0且之前已经调用过了,直接返回之前产生的view
			if (position == 0 && !isFristGetZero) {
				Log.e("getView", "return zero");
				return zeroView;
			}
			//这里生成你的view
			ViewHodler hodler = null;
			if (convertView != null) {
				hodler = (ViewHodler) convertView.getTag();
			} else {
				convertView = LayoutInflater.from(MainActivity.this).inflate(
						88888, null);
				hodler = new ViewHodler();
				convertView.setTag(hodler);
			}
			//position为0,并且为第一次调用,保存view并记录
			if (position == 0 && isFristGetZero) {
				Log.e("getView", "Frist get zero!");
				zeroView = convertView;
				isFristGetZero = false;
			}
			return convertView;
		}

当然在实际中,你要加些逻辑在你的第一个item发生变化时刷新zeroView。



求赞求评论

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值