當加載不同佈局時,需要使用到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進行復用,並對緩存進行重新的設置。