之前有说过adapter如何跟listview产生联系,以及通知listview去全局刷新requestlayout。请参考 浅析listview及Adapter原理一。接下来就要看下全局刷新的时候跟adapter之间又做了什么交互?
(1) listview测量过程中与adapter交互
全局刷新就会调用到视图的onMeasure,onLayout,onDraw流程。我们参考下listview的部分源码段如下:
OnMeasure():
if (heightMode == MeasureSpec.AT_MOST) {
// TODO: after first layout we should maybe start at the first visible position, not 0
heightSize = measureHeightOfChildren(widthMeasureSpec, 0, NO_POSITION, heightSize, -1);
}
measureHeightOfChildren():
for (i = startPosition; i <= endPosition; ++i) {
child = obtainView(i, isScrap);
child = obtainView(i, isScrap);
measureScrapChild(child, i, widthMeasureSpec);
}
!!!obtainView就是创建子view的场所:
scrapView = mRecycler.getScrapView(position);
View child;
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
if (scrapView != null) {
child = mAdapter.getView(position, scrapView, this);
}else{
child = mAdapter.getView(position, null, this);
}
}
这个地方,adapter.getView去获取视图(同时listview中有缓存模块RecyclerBin记录所缓存的视图),稍后接受recyclebin大致缓存策略。
!!!:measureScrapChild()方法:
LayoutParams p = (LayoutParams) child.getLayoutParams();
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
child.setLayoutParams(p);
}
p.viewType = mAdapter. getItemViewType(position);
p.forceAdd = true;
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
child.setLayoutParams(p);
}
p.viewType = mAdapter. getItemViewType(position);
p.forceAdd = true;
int childWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
mListPadding.left + mListPadding.right, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
mListPadding.left + mListPadding.right, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
当前测量首先会对子view本身进行测量,同时用到 adapter.getItemViewType(position) ,这个方法主要是设置当前item的缓存视图数组中的索引,稍后做说明。
(2)listview视图缓存机制跟adapter如何交互?
我们都知道listview内部是有缓存视图的,否则我们数据条目过大的时候,会给UI绘制带来极大的压力。下面我们来看看adapter接口如何来控制缓存视图策略的。
1. getView(position, convertview, viewgroup)接口
相信大家并不陌生,这个接口我们通常会用来定义每条item的视图,并且绑定数据显示。我们也知道convertview一开始的时候是为空的,但是有的时候又不为空可以直接拿来用。这是不是就是视图缓存?
前面我们在测量过程分析 obtainView是去创建视图的,有个方法obtainView中用到了adapter.getView(),大家有没有注意到之前一句代码
scrapView = mRecycler.getScrapView(position); 这个就是通过RecycleBin这个视图缓存管理器去获取缓存视图的方法。
(2) getViewTypeCount()
该方法字面上理解是视图类型的数量, 有点疑惑,通常我们并不会主动去设置这接口,我们看调用它的位置,
我们知道适配器与listview通过setAdapter方法建立联系。
ListView中setAdapter:
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
RecyleBin中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;
}
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;
}
我们可以知道recycleBin中根据viewTypeCount创建一个视图缓存数组,listview可能直接使用缓存视图。
(3)getItemViewType(int position)
前面我们知道listview内部维护了缓存视图数组,那么问题来了,我们每一条item怎么就获取到正确的缓存视图呢。前面obtainview创建视图时候
mRecycler.getScrapView(position)获取缓存视图。
具体源码:
View getScrapView(int position) {
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}
if (mViewTypeCount == 1) {
return retrieveFromScrap(mCurrentScrap, position);
} else {
int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
}
return null;
}
我们发现getItemViewType(position)方法实际是获取视图缓存数组中的索引,找到对应缓存视图。
既然有用到获取视图缓存,自然需要将视图添加到缓存中,我们视图在测量绘制阶段会加入到listview缓存数组中。
void addScrapView(View scrap, int position) {
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
lp.scrappedFromPosition = position;
int viewType = lp.viewType;
final boolean scrapHasTransientState = scrap.hasTransientState();
if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<View>();
}
mSkippedScrap.add(scrap);
}
if (scrapHasTransientState) {
if (mTransientStateViews == null) {
mTransientStateViews = new SparseArray<View>();
}
scrap.dispatchStartTemporaryDetach();
mTransientStateViews.put(position, scrap);
}
return;
}
scrap.dispatchStartTemporaryDetach();
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
scrap.setAccessibilityDelegate(null);
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
if (mRecyclerListener != null) {
mRecyclerListener.onMovedToScrapHeap(scrap);
}
}
以上的viewtype是通过当前AbsListView.Layoutparam获取,而前面
measureScrapChild方法中
LayoutParams p = (LayoutParams) child.getLayoutParams();
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(position);
p.forceAdd = true;
if (p == null) {
p = (AbsListView.LayoutParams) generateDefaultLayoutParams();
child.setLayoutParams(p);
}
p.viewType = mAdapter.getItemViewType(position);
p.forceAdd = true;
则将视图缓存索引添加进LayoutParams
总结:以上是adapter跟listview视图缓存建立关系,getViewTypeCount用于产生视图缓存容器大小,getItemViewType(int position)则定位当前视图缓存容器的索引,getView则是用于创建视图,或者拿缓存视图进行更新。
(如果有问题的请各位大神指正!!!)