Android之GridView控制顯示多少行以及遇到的怪事


前段時間接到一個需求,要求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時候去注冊的,當數據加載完畢就移除監聽,原來以為很完美沒想到會有這種情況發生。那知道了問題在哪就好對症下葯了,直接把添加監聽的方法放在拿到數據后執行就好了,每次到數據就監聽處理,處理完了就移除監聽,下次數據來了依然也可以依次執行。

代碼就不貼了,說的很清楚了,此處應掃二維碼關注公眾號了!

 




 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM