我们往往放入数据就直接这样:
listView.adapter=adapter
实现机制到底是怎么样的呢,那么我们来看看listView的setAdapter方法是怎么实现的吧:
ListView.java
public void setAdapter(ListAdapter adapter) {
....
requestLayout();
....
}
AbsListView.java---ListView父类
public void requestLayout() {
if (!mBlockLayoutRequests && !mInLayout) {
//最后还是View的requestLayout
super.requestLayout();
}
}
这不就是View的绘制过程嘛。我们知道ListView它是一个ViewGroup,requestLayout方法,就是View的OnMeasure,OnLayout,OnDraw方法,对于OnMeasure方法,并没有什么特别的地方,终归是个View,占用的空间最多也就整个屏幕,对于OnDraw,ListView本身不参与绘制,都是由listView当中的子元素来绘制的,那我们来看看OnLayout方法的具体实现吧,这段比较长,得跟着分析去看代码:
AbsListView.java
protected void onLayout(boolean changed, int l, int t, int r, int b) {
......
//是空实现,我们得看ListView的实现
layoutChildren();
.......
}
ListView.java:
//ListView的layoutChildren很长很长,我们就看关键的一部分吧
protected void layoutChildren() {
.......
// Pull all children into the RecycleBin.
//看这RecycleBin注释就知道,这块跟缓存有很大的关系,我们稍后再看缓存这块内容
final RecycleBin recycleBin = mRecycler; //-------------1
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();//---------------2
.....
if (childCount == 0) {//------------3
.....
//会调用fillDown方法
sel = fillFromTop(childrenTop);
.....
}
//nextTop是第一个子元素顶部距离整个ListView顶部的像素值
//pos则是刚刚传入的mFirstPosition的值
private View fillDown(int pos, int nextTop) {
View selectedView = null;
//end是ListView底部减去顶部所得的像素值
int end = (mBottom - mTop);
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
//子元素已经超出当前屏幕了,或者pos大于等于mItemCount时,也就是所有Adapter中的元素都被遍历结束了
while (nextTop < end && pos < mItemCount) {
// is this the selected item?
boolean selected = pos == mSelectedPosition;
View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
nextTop = child.getBottom() + mDividerHeight;
if (selected) {
selectedView = child;
}
pos++;
}
setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
return selectedView;
}
//先从缓存中获取View,如果没有则obtainView
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
.....
//这个方法是在父类实现的
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
//这方法就是绘制child
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
AbsListView.java:
View obtainView(int position, boolean[] outMetadata) {
.....
//这里这个方法很熟悉吧
final View child = mAdapter.getView(position, scrapView, this);
......
return child;
}
我们再来回顾下整个流程:ListView.setAdapter会调用requestLayout方法,这个方法其实就是View的绘制过程,由于ListView是个GroupView,只需看onLayout方法,ListView使用onLayoutChildren来实现onLayout方法的。在这个方法中,根据Adapter里面的count值来循环“生成”子View,每生成一个子View就绘制这个view
那我们来讲讲ListView缓存机制吧,我们先来看看RecycleBin这个类RecycleBin这个类比较重要,源码有注释:
* The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
* storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
* start of a layout. By construction, they are displaying current information. At the end of
* layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
* could potentially be used by the adapter to avoid allocating views unnecessarily.
看这注释知道这个类主要是为了有两级缓存 ActiveViews and ScrapViews, mCurrentScrap
/**
* Views that were on screen at the start of layout. This array is populated at the start of
* layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
* Views in mActiveViews represent a contiguous range of Views, with position of the first
* view store in mFirstActivePosition.
*/
private View[] mActiveViews = new View[0];
/**
* Unsorted views that can be used by the adapter as a convert view.
*/
private ArrayList[] mScrapViews;
private ArrayList mCurrentScrap;
private ArrayList mSkippedScrap;
在上述的流程中第一次出现RecycleBin这个对象的地方就是layoutChildren方法中,接下来我们来看看RecycleBin的addScrapView以及fillActiveViews方法:
ListView.java:
protected void layoutChildren() {
......
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
}
} else {
recycleBin.fillActiveViews(childCount, firstPosition);
}
......
}
AbsListView&RecycleBin.java:
void addScrapView(View scrap, int position) {
........
//这里childView缓存
if (mViewTypeCount == 1) {
mCurrentScrap.add(scrap);
} else {
mScrapViews[viewType].add(scrap);
}
.......
}
这个方法可以看出mScrapViews和mCurrentScrap算是同级的缓存了,这两个的区别就是ViewType。然后再看看 recycleBin的fillActiveViews:
/**
* Fill ActiveViews with all of the children of the AbsListView.
*
* @param childCount The minimum number of views mActiveViews should hold
* @param firstActivePosition The position of the first view that will be stored in
* mActiveViews
*/
//firstActivePosition表示第一个可见元素的position值
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
//headView和footView不能放入
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这个缓存跟上述的mScrapViews和mCurrentScrap不同的是没有区别ViewType
接下来我们看看makeAndAddView方法。根据上面分析整个流程后,我们知道,在这个方法里面,先从缓存中取出View,如果没有才新建一个View:
/**
* Obtains the view and adds it to our list of children. The view can be
* made fresh, converted from an unused view, or used as is if it was in
* the recycle bin.
*
* @param position logical position in the list
* @param y top or bottom edge of the view to add
* @param flow {@code true} to align top edge to y, {@code false} to align
* bottom edge to y
* @param childrenLeft left edge where children should be positioned
* @param selected {@code true} if the position is selected, {@code false}
* otherwise
* @return the view that was added
*/
private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
if (!mDataChanged) {
// Try to use an existing view for this position.
final View activeView = mRecycler.getActiveView(position);
if (activeView != null) {
// Found it. We're reusing an existing child, so it just needs
// to be positioned like a scrap view.
//Adds a view as a child and make sure it is measured (if necessary) and positioned properly.主要就是加入子元素,并测绘
setupChild(activeView, position, y, flow, childrenLeft, selected, true);
return activeView;
}
}
// Make a new view for this position, or convert an unused view if
// possible.
final View child = obtainView(position, mIsScrap);
// This needs to be positioned and measured.
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}
在这个方法中,我们看到了getActiveView方法:
/**
* Get the view corresponding to the specified position. The view will be removed from
* mActiveViews if it is found.
*
* @param position The position to look up in mActiveViews
* @return The view if it is found, null otherwise
*/
View getActiveView(int position) {
int index = position - mFirstActivePosition;
final View[] activeViews = mActiveViews;
if (index >=0 && index < activeViews.length) {
final View match = activeViews[index];
activeViews[index] = null;
return match;
}
return null;
}
值得注意的是,它会把相应的位置的View全部取出来,然后置为null,我们着重 跟踪 obtainView方法内部是如何实现的:
View obtainView(int position, boolean[] outMetadata) {
.........
// Check whether we have a transient state view. Attempt to re-bind the
// data and discard the view if we fail.
//这里是与View的hasTransientState()方法有关,不需要深入了解
//其目的是通过参数position的值来获取刚消失的View对象
final View transientView = mRecycler.getTransientStateView(position);
........
final View scrapView = mRecycler.getScrapView(position);
///----------------------1
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 if (child.isTemporarilyDetached()) {
outMetadata[0] = true;
// Finish the temporary detach started in addScrapView().
child.dispatchFinishTemporaryDetach();
}
}
......
return child;
}
View getScrapView(int position) {
//这里比较熟悉了,获取View的类型
final int whichScrap = mAdapter.getItemViewType(position);
if (whichScrap < 0) {
return null;
}
if (mViewTypeCount == 1) {
//也就是从数组里面取出View,然后把数组相应的位置的值置为null
return retrieveFromScrap(mCurrentScrap, position);
} else if (whichScrap < mScrapViews.length) {
return retrieveFromScrap(mScrapViews[whichScrap], position);
}
return null;
}
很明显就是从那mCurrentScrap|mScrapViews数组中取出相应的View。值得注意的是标记1的地方,这就是我们经常重写getView方法需要判空的原因了,传入的scrapView是缓存中的View,它也有可能会为null的
结合这两种缓存的方式,先从数组mActiveViews中取出缓存的View,取出来的同时还会移除掉这个缓存View,如果没有,则进行下一个缓存的读取,就算这个缓存中读取到了View,但是还是需要通过Adapter.getView()方法去封装View。