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

前段時間接到一個需求,要求GridView超過兩行只顯示兩行多餘的不顯示。可是GridView沒有設置多少行的api,只有設置多少列的方法,處處查找資料都相似的case,stakeoverfrow上面也沒什麼有價值的答案,不過在百度知道居然看到了一個思路。android

知道:http://zhidao.baidu.com/link?url=f-F4MnuKApNqgTzcW0r7nUDljHch3v-iGV7LspkMFW97ftxgh0JJSwhdRYkipzK4zdyBoWfJZ9ZakJoAnBppKqapi

 

第一個方案:說定死列數,算出總數,只有顯然不科學,由於個人gridview列數十須要autofit的,隨手機屏幕多寬就相應顯示多少列。數組

gridview的xml屬性 android:numColumns="6"在adapter裏重寫@Override public int getCount() { return 12; }這樣應該行吧

第二個方案:限定gridview高度,由於項目時間很緊因此腦子裏也曾蹦出這個想法,試着寫死一個固定高度,但事實上這高度是不起做用的,由於它是根據adapte的條目來計算的。app

第三個方案:其實就是第一個方案改良版,雖然是很樸素的思路可是能夠改活。在探索過程當中發現gridview在高版api中有一個獲取列數的方法,若是知道到列數那就好辦了,列數*2就獲得了要顯示兩行的count,這樣既知足列數自適應又很優雅的控制要顯示的區域。異步

=====================================================================ide

因而代碼能夠寫下去了:佈局

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裏面果真能獲取到了。ui

剛纔說到getNumColumns()是高版方法,API11以上纔有,因此還得back compat。這個只要網上搜索一下一大把,我也是摘抄了一段。this

/**
     * 獲取列數的兼容方法.
     * @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時候去註冊的,當數據加載完畢就移除監聽,原來覺得很完美沒想到會有這種狀況發生。那知道了問題在哪就好對症下藥了,直接把添加監聽的方法放在拿到數據後執行就行了,每次到數據就監聽處理,處理完了就移除監聽,下次數據來了依然也能夠依次執行。

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

 

相關文章
相關標籤/搜索