ListView加載不同佈局時的複用及原理分析

當加載不同佈局時,需要使用到getViewTypeCount和getItemViewType。首先來看看如何來實現加載不同而已時的複用

步驟:
重(@Override)寫 getViewTypeCount() – 返回你有多少個不同的佈局
重寫 getItemViewType(int) – 由position返回view type id
根據view item的類型,在getView中創建正確的convertView

代碼如下:

private class MyAdapter extends BaseAdapter {

@Override
public int getCount() {
return list.size();
        }

@Override
public Object getItem(int position) {
return position;
        }

@Override
public long getItemId(int position) {
return 0;
        }

/**
         * 視圖類型的個數
* @return
*/
@Override
public int getViewTypeCount() {
return 2;
        }

/**
         * 返回不同位置的視圖的類型
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {

if (position % 2 == 0) {
return 1;
            } else {
return 2;
            }

        }

@Override
public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder;
if (convertView == null) {
                holder = new ViewHolder();
if (getItemViewType(position) == 1) {
                    convertView = View.inflate(MutipleItemListActivity.this, R.layout.item_listview_left, null);
                } else {
                    convertView = View.inflate(MutipleItemListActivity.this, R.layout.item_listview_right, null);
                }
                holder.textView = (TextView) convertView.findViewById(R.id.textView);
                convertView.setTag(holder);

            } else {
                holder = (ViewHolder) convertView.getTag();
            }


            String text = list.get(position);
            holder.textView.setText(text);

return convertView;
        }
    }


static class ViewHolder {
        TextView textView;
    }
}

其實很簡單,無非就是在getView方法中調用調用getItemViewType方法去判斷類型,從而填充不同的佈局。

下面來分析一下利用的原理
既然有複用,就有緩存,那麼ListView的item是在哪裏緩存的呢?
分析原碼要有一個入口,ListView的入口無疑是setAdapter方法,因爲我們第一個調用的就是它。
源碼的中有一名
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
getViewTypeCount方法設置的視圖類型數量被mRecycler獲取。繼續追蹤,可以發現, mRecycler爲AbListView的一個內部類,而它就是我們要找的緩存所在RecycleBin 。RecycleBin 中有兩個成員變量數組, mActiveViews與mScrapView 。從字面上就可以理解,mActiveViews爲活動中的view,即顯示在界面上的item, 而mScrapView則緩存着用來利用的item.
而外還有一個LongSparseArray mTransientStateViewsById, 保存着mScrapView中空閒view的引用 。
繼續看setViewTypeCount

public void setViewTypeCount(int viewTypeCount) {
    if (viewTypeCount < 1) {
        throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
    }
    //noinspection unchecked
    ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
    for (int i = 0; i < viewTypeCount; i++) {
        scrapViews[i] = new ArrayList<View>();
    }
    mViewTypeCount = viewTypeCount;
    mCurrentScrap = scrapViews[0];
    mScrapViews = scrapViews;
}

這個方法只是對mScrapView作的一個初始化。而mScrapView的大小 就是viewTypeCount. 接下來要通過搜索mRecycler.addScrapView發現第一次調用在obtainView方法中,這個方法會調用getView完成視圖的初始化工作.

final View transientView = mRecycler.getTransientStateView(position);
if (transientView != null) {
if (params.viewType == mAdapter.getItemViewType(position)) {
    final View updatedView = mAdapter.getView(position, transientView, this);

    // If we failed to re-bind the data, scrap the obtained view. 這裏只是不使用複用時纔會執行下去
    if (updatedView != transientView) {
        setItemViewLayoutParams(updatedView, position);
        mRecycler.addScrapView(updatedView, position);
    }
}
final View scrapView = mRecycler.getScrapView(position);
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
    if (child != scrapView) {
        // Failed to re-bind the data, return scrap to the heap.
        mRecycler.addScrapView(scrapView, position);
    } else {
        isScrap[0] = true;

        // Finish the temporary detach started in addScrapView().
        child.dispatchFinishTemporaryDetach();
    }
}

代碼分兩部分,一部分是從空閒的集合mTransientStateViewsById中到取view時行利用,
如果沒取到,則直接從mScrapViews中取。可以發現,這裏並沒有填充mScrapViews. 還有一個地方執行addScrapView方法的則是在
trackMotionScroll方法中,這個方法會在界面滑動時會被調用。

for (int i = childCount - 1; i >= 0; i--) {
    final View child = getChildAt(i);
    if (child.getTop() <= bottom) {
        break;
    } else {
        start = i;
        count++;
        int position = firstPosition + i;
        if (position >= headerViewsCount && position < footerViewsStart) {
            // The view will be rebound to new data, clear any
            // system-managed transient state.
            child.clearAccessibilityFocus();
            mRecycler.addScrapView(child, position);
        }
    }
}

mScrapViews會把所有滑過的item的條目的引用都保存起來,以便以後的複用,

View getScrapView(int position) {
    final int whichScrap = mAdapter.getItemViewType(position);
    if (whichScrap < 0) {
        return null;
    }
    if (mViewTypeCount == 1) {
        return retrieveFromScrap(mCurrentScrap, position);
    } else if (whichScrap < mScrapViews.length) {
        return retrieveFromScrap(mScrapViews[whichScrap], position);
    }
    return null;
}

getScrapView先判斷類型,然後調用retrieveFromScrap取出數組中相應位置的view, 同時刪除當前位置的引用。

fillActivityViews則會填充所有活動中item

void fillActiveViews(int childCount, int firstActivePosition) {
    if (mActiveViews.length < childCount) {
        mActiveViews = new View[childCount];
    }
    mFirstActivePosition = firstActivePosition;

    //noinspection MismatchedReadAndWriteOfArray
    final View[] activeViews = mActiveViews;
    for (int i = 0; i < childCount; i++) {
        View child = getChildAt(i);
        AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
        // Don't put header or footer views into the scrap heap
        if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
            // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
            //        However, we will NOT place them into scrap views.
            activeViews[i] = child;
            // Remember the position so that setupChild() doesn't reset state.
            lp.scrappedFromPosition = firstActivePosition + i;
        }
    }
}

那麼 mActiveViews與mScrapView的添加還是要回到ListView中

  protected void layoutChildren()   
    {  
        if (dataChanged)   
        {  
            for (int i = 0; i < childCount; i++)   
            {  
                recycleBin.addScrapView(getChildAt(i));     
            }  
        } else   
        {  
            recycleBin.fillActiveViews(childCount, firstPosition);  
        }  
        ....  
    }  

當ListView執行OnLayout時,會調用layoutChildren方法,並填充 mActiveViews與mScrapView。 當數據項發生變化時,調用addScrapView。

再看一個重要的方法makeAndAddView,這個方法將決定從哪裏取取出child,並繪製出來。

private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
        boolean selected) {
    View child;


    if (!mDataChanged) {
        // Try to use an existing view for this position
        child = mRecycler.getActiveView(position);
        if (child != null) {
            // Found it -- we're using an existing child
            // This just needs to be positioned
            setupChild(child, position, y, flow, childrenLeft, selected, true);

            return child;
        }
    }
    // Make a new view for this position, or convert an unused view if possible
    child = obtainView(position, mIsScrap);

    // This needs to be positioned and measured
    setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);

    return child;
}

分析的有點亂。總結下,也就是mActiveViews保存活動中的item, mScrapView 保存着未被顯示出來的位置的item.當數據項發生變化,或者滑動時,會取出 mScrapView中的item進行復用,並對緩存進行重新的設置。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章