第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用来显示图片,并且用了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。