文章参考:https://blog.csdn.net/guolin_blog/article/details/44996879
ListView缓存机制所需的数据结构
class RecycleBin {
private View[] mActiveViews = new View[0];
private ArrayList<View>[] mScrapViews;
private ArrayList<View> mCurrentScrap;
ListView有两层缓存: mActiveViews mScrapViews
mActiveViews:View类型数组,保存ListView当前整个屏幕的所有itemView,mActiveViews只用于在第二次layout时使itemView快速显示到屏幕中,使用完毕后就会将数组元素置为null
mScrapViews:ArrayList<View>类型数组,用来保存ListView移出屏幕外的itemView
ListView缓存数组的初始化
1.mActiveViews的初始化:在 RecycleBin的fillActiveViews()方法里完成mActiveViews的填充,相关源码如下
void fillActiveViews(int childCount, int firstActivePosition) {
if (mActiveViews.length < childCount) {
mActiveViews = new View[childCount];
}
mFirstActivePosition = firstActivePosition;
final View[] activeViews = mActiveViews;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
//子view的布局参数中会保存该子view类型、位置等信息
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
//Header和Footer类型的子view将不被添加到 mActiveViews中
if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
activeViews[i] = child;
lp.scrappedFromPosition = firstActivePosition + i;
}
}
}
接下来看一下 mActiveViews是何时完成初始化的,这里要特别注意一个点就是:mActiveViews的初始化是在ListView的第二次onLayout()执行的时候进行的初始化,因为第一次layout将会调Adapter的getView方法,在getView方法里最终通过 LayoutInflater的inflate方法去创建view,创建完成后再通过ViewGroup的 addViewInLayout方法添加到父容器ListView中,所以第一次onLayout执行时 childCount=0,第一次layout完成后 childCount才不等于0,接下来看一下第二次layout 初始化 mActiveViews的关键代码
public class ListView extends AbsListView {
@Override
protected void layoutChildren() {
......
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
//第二次layout时就是在这里完成 mActiveViews的初始化
recycleBin.fillActiveViews(childCount, firstPosition);
}
//将ListView所有子view detatch是为了避免加载重复数据
detachAllViewsFromParent();
//将 mActiveViews 中的每个元素置为null
//注意:置为null之前会调用makeAndAddView()
recycleBin.scrapActiveViews();
......
}
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
//第二次layout时activeView将不为空,因为在这之前已经调用了layoutChildren
//layoutChildren里已经调用了 fillActiveViews
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
//这里obtainView返回的就是第一次layout时通过LayoutInflater创建的View
//obtainView是父View AbsListView的方法
final View child = obtainView(position, mIsScrap);
//setUpChild里完成子view的测量和布局
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
}
注意:第二次layout调用完 makeAndAddView 之后便会调用 RecycleBin的scrapActiveViews()方法将 mActiveViews置为null
所以 mActiveViews的初始化是在第二次layout时在 ListView的 layoutChildren方法里,通过调用RecycleBin的fillActiveViews()方法完成的,并且 mActiveViews 仅仅用于在第二次layout时快速显示itemView
2. mScrapViews的初始化:在 RecycleBin的addScrapView()方法里完成 mScrapViews 的填充,相关源码如下
void addScrapView(View scrap, int position) {
final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
final boolean scrapHasTransientState = scrap.hasTransientState();
if (scrapHasTransientState) {
......
} else {
clearScrapForRebind(scrap);
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
}
通过源码可以知道,当ListView的子view类型只有一种时,将会使用 mCurrentScrap 来存储废弃的子view(比如滑出屏幕的子view),当子view类型不止一种时将会以子view的viewType作为下标,mScrapViews[viewType]将会返回一个 ArrayList<View>类型的view列表,然后直接将废弃的子view添加到 mScrapViews 中,其中viewType是通过子view的LayoutParams来获取的,接下来看一下mScrapViews 是何时初始化的在AblistView的onTouchEvent()方法里,在处理ACTION_MOVE事件时会去调用trackMotionScroll()方法来跟踪手指滑动事件
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
......
final boolean down = incrementalDeltaY < 0;
......
if (down) {//手指上滑
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
child.clearAccessibilityFocus();
//子view滑出屏幕时会被回收进mScrapViews
mRecycler.addScrapView(child, position);
}
}
}
} else {//手指下滑
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
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) {
child.clearAccessibilityFocus();
//子view滑出屏幕时会被回收进mScrapViews
mRecycler.addScrapView(child, position);
}
}
}
}
......
return false;
}
mScrapView的回收复用
最后看一下 mScrapView是怎么回收再利用的,在手指滑动过程中部分子View被滑出屏幕之外,所以ListView还需要新的子View来填充空白,这个操作又是通过调用ListView的makeAndAddView()方法来实现的
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
//注意:因为在第二次layout时mActiveViews已被清空,所以这里获取到的activeView肯定为null
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
//由于activeView为null,将会调用ontainView方法获取新的View
final View child = obtainView(position, mIsScrap);
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
obtainView是ListView的父类AblistView中的方法
View obtainView(int position, boolean[] outMetadata) {
......
//获取mScrapView中的废弃子view
final View scrapView = mRecycler.getScrapView(position);
//注意这里将scrapView作为参数传入了getView方法
final View child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
if (child != scrapView) {
mRecycler.addScrapView(scrapView, position);
} else if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
child.dispatchFinishTemporaryDetach();
}
}
if (mCacheColorHint != 0) {
child.setDrawingCacheBackgroundColor(mCacheColorHint);
}
......
return child;
}
接下来看一个重写 getView方法的例子
public class FruitAdapter extends ArrayAdapter<Fruit> {
private int resourceId;
public FruitAdapter(Context context, int textViewResourceId, List<Fruit> objects){
super(context,textViewResourceId,objects);
resourceId=textViewResourceId;
}
@Override
public View getView(int position,View convertView,ViewGroup parent){
Fruit fruit=getItem(position);
/*
这里的convertView就是上面的scrapView传进来的,当convertView不为null时就可以
直接拿来用,而不需要再通过LayoutInflater加载,大大节省效率
*/
if(convertView == null){
convertView = LayoutInflater.from(getContext()).inflate(resourceId,parent,false);
}
ImageView fruitImage=(ImageView)convertView .findViewById(R.id.fruit_image);
TextView fruitName=(TextView) convertView .findViewById(R.id.fruit_name);
fruitImage.setImageResource(fruit.getImageId());
fruitName.setText(fruit.getName());
return convertView ;
}
}