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。



求贊求評論


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com