前段時間接到一個需求,要求GridView超過兩行只顯示兩行多余的不顯示。但是GridView沒有設置多少行的api,只有設置多少列的方法,到處查找資料都類似的case,stakeoverfrow上面也沒什么有價值的答案,不過在百度知道竟然看到了一個思路。
知道:http://zhidao.baidu.com/link?url=f-F4MnuKApNqgTzcW0r7nUDljHch3v-iGV7LspkMFW97ftxgh0JJSwhdRYkipzK4zdyBoWfJZ9ZakJoAnBppKq
第一個方案:說定死列數,算出總數,只有顯然不科學,因為我的gridview列數十需要autofit的,隨手機屏幕多寬就相應顯示多少列。
gridview的xml屬性 android:numColumns="6"在adapter里重寫@Override public int getCount() { return 12; }這樣應該行吧
第二個方案:限定gridview高度,因為項目時間很緊所以腦子里也曾蹦出這個想法,試着寫死一個固定高度,但事實上這高度是不起作用的,因為它是根據adapte的條目來計算的。
第三個方案:其實就是第一個方案改良版,雖然是很朴素的思路但是可以改活。在探索過程中發現gridview在高版api中有一個獲取列數的方法,如果知道到列數那就好辦了,列數*2就得到了要顯示兩行的count,這樣既滿足列數自適應又很優雅的控制要顯示的區域。
=====================================================================
於是代碼可以寫下去了:
int numberOfColumns = mGridView.getNumColumns(); if (numberOfColumns > 0) {//adapter item 數據已填充 int countOf2Row = numberOfColumns * 2; for (int i = count2Row - 1; i < couponList.size(); i++) mDataSources.remove(i);//多出2列的數據cut掉 if (mAdapter != null) mAdapter.notifyDataSetChanged(); }
這個if死活沒進去啊,打印出來看到獲取到的NumColumn是-1,然后看到方法doc:
Get the number of columns in the grid.
* Returns {@link #AUTO_FIT} if the Grid has never been laid out.
我明明是在adapte.notifyDatasetChanged()方法之后去獲取的啊,怎么還是拿不到,查閱了一些資料之后就監聽gridview布局變化,在listener里面果然能獲取到了。
剛才說到getNumColumns()是高版方法,API11以上才有,所以還得back compat。這個只要網上搜索一下一大把,我也是摘抄了一段。
/** * 獲取列數的兼容方法. * @return */ public int getNumColumnsCompat() { if (Build.VERSION.SDK_INT >= 11) { return getNumColumnsCompat11(); } else { int columns = 0; int children = getChildCount(); if (children > 0) { int width = getChildAt(0).getMeasuredWidth(); if (width > 0) { columns = getWidth() / width; } } return columns > 0 ? columns : AUTO_FIT; } } @TargetApi(Build.VERSION_CODES.HONEYCOMB) private int getNumColumnsCompat11(){ return getNumColumns(); }
那最后代碼變成這樣了:
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int numberOfColumns = ((MyGridView) mGridView).getNumColumnsCompat(); if (numberOfColumns > 0) {//adapte item 數據已填充 int count2Row = numberOfColumns * 2; for (int i = count2Row - 1; i < couponList.size(); i++) list.remove(i);//多出2列的數據cut掉 if (mAdapter != null) mAdapter.notifyDataSetChanged(); ApiVersionCompat.removeOnGlobalLayoutListener(mGridView.getViewTreeObserver(), this); } } });
注意在onGlobalLayout處理好移除數據的邏輯之后需要移除監聽器哦,移除監聽也有一個版本差異,代碼送上:
public static void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener victim) { if (Build.VERSION.SDK_INT >= 16) { observer.removeOnGlobalLayoutListener(victim); } else { observer.removeGlobalOnLayoutListener(victim); } }
本以為就perfect了事了,feature也實現了,資源也節約了(因GroblaLayoutListener會多次觸發,所以一定要做好限制,用完就釋放掉不要讓代碼重復執行了)。代碼交上去,apk發給測試測了之后bug就丟過來了,測試出來說還是有不止兩行的grid出現啊,我心里怎么也不相信啊,怎么可能我明明自測過的。

當我重新打開app,發現確實有超出2行多出一個Item掉在第三行,后來就懷疑是不是cut數據沒控制好,代碼明明是for (int i = count2Row - 1; i < list.size(); i++)這樣循環啊。每次都多出一個,難道i<=list.size(),我也曾很多次懷疑過怎么可能會list.remove(list.size()),不可能不可能不可能啊,這樣絕對會數組越界啊,難不成是i初始還要count2Row-2,試過之后還是如此多一個(因為總數是有9個,從第六個開始剪掉2個也還是有一個)。
log出size讓我驚訝了,size竟然是6,但接口出來明明是9啊。然后就猛然一醒,加載數據和觸發布局改變事件應該是異步的,也就是說當我在布局改變事件中獲取到列數>0時,並不一定數據全部加載完,而這時我已經移除監聽了。當加載到第1行數據的時候就可以獲取到列數了,恰好此時我移除了其中幾個(如果第一次獲取到的size大於2列之和小於最終size),但當處理完這塊邏輯的同時數據其實還是在加載的,雖然都是微乎其微的瞬間,但也有先來后到的順序,所以移除掉一部分后面又添加了一部分,結果就出現了超過兩行的情況。
mGridView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int numberOfColumns = ((MyGridView) mGridView).getNumColumnsCompat();//有可能每次會有變動 if (numberOfColumns > 0) {//adapter item 數據已填充 int countOf2Row = numberOfColumns * 2; if (list.size() > countOf2Row) { //超出2行(第三行開始)的數據cut掉 for (int i = countOf2Row; i < list.size(); i++) { mAdapter.remove(i); } if (mAdapter != null) mAdapter.notifyDataSetChanged(); } else { //每次檢測到數據集合大小<=countOf2Row的時候就可以移除監聽了 ApiVersionCompat.removeOnGlobalLayoutListener(mGridView.getViewTreeObserver(), this); } } } });
好了,代碼寫好了,自測一下也都OK了,多測幾個不一樣的數據也正常了。可該死的,不小心按到了home鍵,當打開recent列表點回app的時候發現大事不好,又整整齊齊得出現三行滿滿的。

原來這個界面是會在onResure刷新數據的,View都還是那些View,但是數據會重新添加,第一次完美顯示2行,但數據一刷新,就會先clear掉數據源重新添加數據,而此時的GlobalLayoutListener已經移除了無法監聽處理多出2行的邏輯了,所以如上圖所示,完完整整的出現了3行數據。因為我addOnGlobalLayoutListener是在initViews時候去注冊的,當數據加載完畢就移除監聽,原來以為很完美沒想到會有這種情況發生。那知道了問題在哪就好對症下葯了,直接把添加監聽的方法放在拿到數據后執行就好了,每次到數據就監聽處理,處理完了就移除監聽,下次數據來了依然也可以依次執行。
代碼就不貼了,說的很清楚了,此處應掃二維碼關注公眾號了!

