ListView加载不同布局时的复用及原理分析

时间:2023-01-31 19:33:05

当加载不同布局时,需要使用到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进行复用,并对缓存进行重新的设置。