Android之ListView的getItemViewType和getViewTypeCount

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

 

學習內容:android

1.getItemViewType和getViewTypeCount數組

  getItemViewType和getViewTypeCount是ListView中實現複雜列表的兩個相關的方法,普通的ListView中Item是相同的,那麼咱們只須要實現Adapter中四個抽象方法便可,可是若是頁面中Item長得比較的複雜呢?好比說這個。數據結構

 

  好比說這個列表項,其實也不是很複雜,這種類型的Item也有其餘的實現方式,好比說在Adapter中實現SectionIndexer也是能夠實現的,可是咱們就拿這個來講明一下問題,若是一個Item第一種類型是TextView,第二種類型是ImageView+Button+TextView呢,那麼這樣複雜的列表咱們就須要使用getItemViewType()和getTypeViewCount()兩個方法去實現了。這兩個方法理解起來仍是比較容易的,獲取Item中Type的類型以及Item中Type的相關數量。廢話就很少說了,直接說實現方式。ide

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的時候就會出現異常. this

  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.這就是數組越界的真正緣由。spa

 基本就解釋完了,貼上一個從GitHub蕩下來的源代碼:Demo下載3d

相關文章
相關標籤/搜索