Android之ListView的getItemViewType和getViewTypeCount

來源:互聯網
上載者:User

標籤:git   null   cti   問題   extend   []   splay   為什麼   資料結構   

PS:感覺這兩個方法其實還是很容易理解的,也算是給我其他兩個朋友寫的吧,幫他們搞清楚這兩個方法的用法和概念。同時還有一些小細節問題需要注意。

 

學習內容:

1.getItemViewType和getViewTypeCount

  getItemViewType和getViewTypeCount是ListView中實現複雜列表的兩個相關的方法,普通的ListView中Item是相同的,那麼我們只需要實現Adapter中四個抽象方法即可,但是如果頁面中Item長得比較的複雜呢?比如說這個。

 

  比如說這個清單項目,其實也不是很複雜,這種類型的Item也有其他的實現方式,比如說在Adapter中實現SectionIndexer也是可以實現的,但是我們就拿這個來說明一下問題,如果一個Item第一種類型是TextView,第二種類型是ImageView+Button+TextView呢,那麼這樣複雜的列表我們就需要使用getItemViewType()和getTypeViewCount()兩個方法去實現了。這兩個方法理解起來還是比較容易的,擷取Item中Type的類型以及Item中Type的相關數量。廢話就不多說了,直接說實現方式。

public class ListAdapter extends BaseAdapter {    /**     * Item類型,int值.必須從0開始依次遞增.     * */    private static final int TYPE_TITLE = 0;    private static final int TYPE_CONTENT = 1;    /**     * Item Type 的數量     * */    private static final int TYPE_ITEM_COUNT = 2;    /**     * 資料     * */    private List<Company> mData = new ArrayList<>();    private Context context;    public ListAdapter(Context context,List<Company>mData){        this.context = context;        this.mData = mData;    }    @Override    public int getCount() {        return mData.size();    }    @Override    public Object getItem(int position) {        return mData.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup viewGroup) {        /**         * 不同類型的ViewHolder         * */        TitleViewHolder titleViewHolder = null;        CompanyViewHolder contentViewHolder = null;        /**         * 對類型進行判斷,分別inflate不同的布局.         * */        switch (getItemViewType(position)){            case TYPE_TITLE:                titleViewHolder = new TitleViewHolder();                if(convertView == null){                    convertView = View.inflate(context, R.layout.view_holder_company_index,null);                    titleViewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);                    //setTag()                    convertView.setTag(titleViewHolder);                }else{                    //getTag();                    titleViewHolder = (TitleViewHolder) convertView.getTag();                }                titleViewHolder.title.setText(mData.get(position).getName());                break;            case TYPE_CONTENT:                contentViewHolder = new CompanyViewHolder();                if(convertView == null){                    convertView = View.inflate(context,R.layout.view_holder_company,null);                    contentViewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);                    convertView.setTag(contentViewHolder);                }else{                    contentViewHolder = (CompanyViewHolder) convertView.getTag();                }                contentViewHolder.content.setText(mData.get(position).getCode());                break;        }        return convertView;    }    /**     * 根據position擷取Item的類型     * */    @Override    public int getItemViewType(int position) {        if(TextUtils.isEmpty(mData.get(position).getCode())){            return TYPE_TITLE;        }else{            return TYPE_CONTENT;        }    }    /**     * 返回Item Type的總數量     * */    @Override    public int getViewTypeCount() {        return TYPE_ITEM_COUNT;    }    static class TitleViewHolder{        TextView title;    }    static class CompanyViewHolder{        TextView content;    }}
  • 首先我們需要為不同的Item設定不同的數值,int值,因為getItemViewType返回的是int值,所以需定義成int,必須從0開始,依次遞增。原因我後續會做出解釋。
  • 重寫getItemViewType和getViewTypeCount方法,getViewTypeCount返回Item的類型總數,getViewTypeCount則需要進行判斷,判斷方式一般都是通過JavaBean中的相關欄位來判斷的,因此這塊不需要過於糾結。只需要根據position擷取Item的具體類型進行判斷然後就返回就可以了。
  • 定義ViewHolder,根據類型的不同需要定義多個ViewHolder,減少findViewById()的次數。
  • 重寫getView()中的相關方法,在getView中首先根據position擷取Item的類型去載入不用的布局,這裡同時會setViewType為不同類型的Item設定RecycleBin,解決ListView由於多個類型Item的複用問題。不清楚RecycleBin機制的讀者可以去看下ListView的複用機制這裡說到了RecycleBin,如果不懂這個機制是看不明白下面的解釋的。
  • 最後根據傳遞過來的資料setAdapter然後為Item進行賦值就完成了。

  大體的一個思路就是這樣實現的,這裡需要說一下為什麼定義Item的類型的時候必須要從0開始,依次遞增,那麼原因是什麼呢?如果我們有三種類型,我們將Item定義成1,2,4,那麼勢必會出現ArrayIndexOutOfBoundsException,也就是所謂的數組越界,我上網查了很多資料都說會出現異常,並且Google也確實標明了,Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned.但是看到這裡就沒有後續了,沒人會去說明這個問題是怎樣發生的,為什麼要這樣去定義。可能我就是閑的蛋疼的那種人,不弄明白確實感到不舒服。我在解釋一下具體的原因:

  其實發生這種情況一般都是我們在下拉的時候出現的問題,在第一次載入第一頁的時候是不會直接出現崩潰現象的,那麼心細的讀者可能會明白這有可能是ListView在複用時出現的問題,其實卻是就是ListView複用機制導致的。我們來看一下這個方法:

/** *ListView在針對不同Item複用時會調用這個方法 *為每一種不同的Item設定一個RecycleBin,用於複用. */public void setViewTypeCount(int viewTypeCount) {       if (viewTypeCount < 1) {        throw new IllegalArgumentException("Can‘t have a viewTypeCount < 1");    }    // noinspection unchecked      /**     * 根據viewTypeCount的數量設定一個ArrayList.     * 同時為每一個Item再設定一個ArrayList,用來儲存ScrapView.     * 相當於一個二維數組來維護每一個Item的ScrapView數組.     * 這裡也就相當於為不同的Item設定單獨的RecycleBin.     */    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];    for (int i = 0; i < viewTypeCount; i++) {       scrapViews[i] = new ArrayList<View>();    }    //儲存ViewTypeCount,也就 = 2    mViewTypeCount = viewTypeCount;    //當前的Scrap是第一個Item的ScrapView數組.    mCurrentScrap = scrapViews[0];    //mScrapViews就儲存了一個二維數組維護的RecycleBin.    mScrapViews = scrapViews;}

 

  這是具體的資料結構,簡單理解就是每一個Item都有對應的ScrapView數組。這裡其實並不是出問題的地方,我們都知道Item一旦被移出了螢幕,首先會Detach掉,然後被加入到mScrapView數組中(廢棄View池),那麼在addScrapView的時候就會出現異常. 

  void addScrapView(View scrap) {          AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();          if (lp == null) {              return;          }          // Don‘t put header or footer views or views that should be ignored          // into the scrap heap          int viewType = lp.viewType;          if (!shouldRecycleViewType(viewType)) {              if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {                  removeDetachedView(scrap, false);              }              return;          }          /**         *核心代碼就是這塊,由於我們mViewTypeCount != 1 的,因此if條件不成立.         *因此會執行else代碼.         */        if (mViewTypeCount == 1) {              dispatchFinishTemporaryDetach(scrap);              mCurrentScrap.add(scrap);          } else {              dispatchFinishTemporaryDetach(scrap);              mScrapViews[viewType].add(scrap);          }            if (mRecyclerListener != null) {              mRecyclerListener.onMovedToScrapHeap(scrap);          }      }  

  問題就在於這個else代碼當中,我們可以看到mScrapViews[viewType].add(scrap)代碼執行了,我們前面圖片上顯示的,mScrapViews[]是很據Item的種類數量new出來的,由於我們Item總數是兩種類型,那麼mScrapViews[].length = 2,但是這裡是mScrapView[viewtype],viewtype是什麼,其實就是我們getItemViewType的傳回值,如果我們將類型定義成2和3,那麼他會訪問mScrapView[2]和mScrapView[3],可想而知,一定會出現ArrayIndexOutOffBoundsException.這就是數組越界的真正原因。

 基本就解釋完了,貼上一個從GitHub蕩下來的原始碼:Demo下載

 

Android之ListView的getItemViewType和getViewTypeCount

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.