Listview部分源码分析

Listview在android开发中算是最常用的几个控件之一,由于要应付各种不同的需求,甚至有时候是奇怪且独特的需要,使用Listview就总会遇到些奇怪的问题,但是其实都没有什么捷径,看源码是最好的办法。而与Listview相关的源码至少有一万行,涉及到AbsListview、AdapterView、ListAdapter等,这里对部分的源码做分析。下面由于代码数量比较多,只要着重看有中文注释的部分就可以了。

 1.与数据adapter相关的

setAdapter()

setAdapter source code
 1 /**
 2      * Sets the data behind this ListView.
 3      *
 4      * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
 5      * depending on the ListView features currently in use. For instance, adding
 6      * headers and/or footers will cause the adapter to be wrapped.
 7      *
 8      * @param adapter The ListAdapter which is responsible for maintaining the
 9      *        data backing this list and for producing a view to represent an
10      *        item in that data set.
11      *
12      * @see #getAdapter() 
13      */
14     @Override
15     public void setAdapter(ListAdapter adapter) {
16         if (null != mAdapter) {
17             mAdapter.unregisterDataSetObserver(mDataSetObserver);    //移除了与当前listview的adapter绑定数据集观察者DataSetObserver
18         }
19 
20         resetList();   //重置listview,主要是清除所有的view,改变header、footer的状态
21         mRecycler.clear();  //清除掉RecycleBin对象mRecycler中所有缓存的view,RecycleBin后面着重介绍,主要是关系到Listview中item的重用机制,它是AbsListview的一个内部类
22 
23         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {   //判断是否有header和footer
24             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);//假如有,用HeaderViewListAdapter包装一下adapter
25         } else {
26             mAdapter = adapter;
27         }
28 
29         mOldSelectedPosition = INVALID_POSITION;
30         mOldSelectedRowId = INVALID_ROW_ID;
31         if (mAdapter != null) {
32             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
33             mOldItemCount = mItemCount;
34             mItemCount = mAdapter.getCount();
35             checkFocus();
36 
37             mDataSetObserver = new AdapterDataSetObserver();
38             //mAdapter注册一个数据集观察者DataSetObserver,DataSetObserver主要是负责侦听adapter中数据的变化,从而实现更新UI
39             mAdapter.registerDataSetObserver(mDataSetObserver);
40 
41             /**mAdapter.getViewTypeCount()的作用其实是有判断item有多少种类型,
42              **一般情况下,item都是一样的只有一种,某些特殊需求中可能就需要多种,然后RecycleBin对象mRecycler记录下item类型的数量*/
43             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
44 
45             int position;
46             if (mStackFromBottom) { //根据mStackFromBottom变量,设置position究竟是顶部第一个还是底部最后一个是selected
47                 position = lookForSelectablePosition(mItemCount - 1, false);
48             } else {
49                 position = lookForSelectablePosition(0, true);
50             }
51             setSelectedPositionInt(position);//AdapterView中的方法,记录当前的position
52             setNextSelectedPositionInt(position);//AdapterView中的方法,记录下一个position
53 
54             if (mItemCount == 0) {
55                 // Nothing selected
56                 checkSelectionChanged();
57             }
58 
59         } else {
60             mAreAllItemsSelectable = true;
61             checkFocus();
62             // Nothing selected
63             checkSelectionChanged();
64         }
65 
66         if (mCheckStates != null) {
67             mCheckStates.clear();//mCheckStates是一个SparseBooleanArray对象,用于保存item的checked状态,performItemClick()、setItemChecked()时,改变内部的值
68         }
69         
70         //就英文吧,Called when something has changed which has invalidated the layout of a child of this view parent. This will schedule a layout pass of the view tree.
71         requestLayout();
72 
73 
74     }

 ListAdapter是通过notifyDataSetChanged实现UI更新的,具体是怎样的呢?

先看registerDataSetObserver,因为registerDataSetObserver是接口Adapter定义的,所以需要实现该接口的类来实现,那么这里以BaseAdapter为例:

registerDataSetObserver()

registerDataSetObserver
1     public void registerDataSetObserver(DataSetObserver observer) {
2         mDataSetObservable.registerObserver(observer);
3     }

它作用就是调用DataSetObservable的registerDataSetObserver,而该方法是在Observable定义的:

registerObserver
 1         /**
 2      * The list of observers.  An observer can be in the list at most
 3      * once and will never be null.
 4      */
 5     protected final ArrayList<T> mObservers = new ArrayList<T>();
 6 
 7     /**
 8      * Adds an observer to the list. The observer cannot be null and it must not already
 9      * be registered.
10      * @param observer the observer to register
11      * @throws IllegalArgumentException the observer is null
12      * @throws IllegalStateException the observer is already registered
13      */
14     public void registerObserver(T observer) {
15         if (observer == null) {
16             throw new IllegalArgumentException("The observer is null.");
17         }
18         synchronized(mObservers) {
19             if (mObservers.contains(observer)) {
20                 throw new IllegalStateException("Observer " + observer + " is already registered.");
21             }
22             mObservers.add(observer);    //把observer添加到mObservers数组中
23         }
24     }

可见registerDataSetObserver的作用其实就是简单的把传进去的mDataSetObserver存储起来。

 

当调用BaseAdapter的notifyDataSetChanged时,干了什么?

notifyDataSetChanged
1     /**
2      * Notifies the attached View that the underlying data has been changed
3      * and it should refresh itself.
4      */
5     public void notifyDataSetChanged() {
6         mDataSetObservable.notifyChanged();
7     }

看mDataSetObservable.notifyChanged()方法:

notifyChanged
 1         /**
 2      * Invokes onChanged on each observer. Called when the data set being observed has
 3      * changed, and which when read contains the new state of the data.
 4      */
 5     public void notifyChanged() {
 6         synchronized(mObservers) {
 7             for (DataSetObserver observer : mObservers) {
 8                 observer.onChanged();    //遍历所有的DataSetObserver调用它们的onChanged方法
 9             }
10         }
11     }

DataSetObserver是一个接口,所以onChanged方法干什么要看具体实现该接口的类,在Listview中,关联的observer是AdapterDataSetObserver类型的,它实现了DataSetObserver,看它的onChanged方法:

onChanged
 1             @Override
 2         public void onChanged() {
 3             mDataChanged = true;
 4             mOldItemCount = mItemCount;
 5             mItemCount = getAdapter().getCount();
 6 
 7             // Detect the case where a cursor that was previously invalidated has
 8             // been repopulated with new data.
 9             if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
10                     && mOldItemCount == 0 && mItemCount > 0) {
11                 AdapterView.this.onRestoreInstanceState(mInstanceState);
12                 mInstanceState = null;
13             } else {
14                 rememberSyncState();
15             }
16             checkFocus();
17             requestLayout();    //onChanged的主要目的就是重布局
18         }

总结一下,上面看出,notifyDataSetChanged方法的目的就是要求Listview重布局,从而实现UI的更新。

既然说到布局,那么接下来就介绍Listview的布局相关的东西。

2.Listview的布局

说到布局,毫无疑问要从onLayout这个方法开始看:

onLayout()

onLayout
 1         /**
 2      * Subclasses should NOT override this method but子类不要重写这个方法,而要重写layoutChildren
 3      *  {@link #layoutChildren()} instead.
 4      */
 5     @Override
 6     protected void onLayout(boolean changed, int l, int t, int r, int b) {
 7         super.onLayout(changed, l, t, r, b);
 8         mInLayout = true;
 9         if (changed) {
10             int childCount = getChildCount();
11             for (int i = 0; i < childCount; i++) {
12                 getChildAt(i).forceLayout();    //保证下一次布局事,子view要重新layout
13             }
14             mRecycler.markChildrenDirty();    //该方法作用时,保证mRecycler中scrapview在下一次布局时要重新布局(mRecycler后面介绍,可以先不管)
15         }
16 
17         layoutChildren();    //这是关键的方法
18         mInLayout = false;
19 
20         mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
21     }

onLayout的关键在于layoutChildren(),所以接下来关键要看layoutChildren:

layoutChildren()

layoutChildren
  1         @Override
  2     protected void layoutChildren() {
  3         final boolean blockLayoutRequests = mBlockLayoutRequests;
  4         if (!blockLayoutRequests) {
  5             mBlockLayoutRequests = true;
  6         } else {
  7             return;
  8         }
  9 
 10         try {
 11             super.layoutChildren();
 12 
 13             invalidate();    //重绘自己,onDraw()会被调用
 14 
 15             if (mAdapter == null) {    //adapter空的话,直接返回,置空listview
 16                 resetList();    
 17                 invokeOnItemScrollListener();
 18                 return;
 19             }
 20 
 21             int childrenTop = mListPadding.top;
 22             int childrenBottom = mBottom - mTop - mListPadding.bottom;
 23 
 24             int childCount = getChildCount();
 25             int index = 0;
 26             int delta = 0;
 27 
 28             View sel;
 29             View oldSel = null;
 30             View oldFirst = null;
 31             View newSel = null;
 32 
 33             View focusLayoutRestoreView = null;
 34 
 35             // Remember stuff we will need down below
 36             switch (mLayoutMode) {    //判断布局模式,根据不同模式,使用不同的布局方法,初始值为LAYOUT_NORMAL
 37             case LAYOUT_SET_SELECTION:
 38                 index = mNextSelectedPosition - mFirstPosition;
 39                 if (index >= 0 && index < childCount) {
 40                     newSel = getChildAt(index);
 41                 }
 42                 break;
 43             case LAYOUT_FORCE_TOP:
 44             case LAYOUT_FORCE_BOTTOM:
 45             case LAYOUT_SPECIFIC:
 46             case LAYOUT_SYNC:
 47                 break;
 48             case LAYOUT_MOVE_SELECTION:
 49             default:
 50                 // Remember the previously selected view
 51                 index = mSelectedPosition - mFirstPosition;
 52                 if (index >= 0 && index < childCount) {    //保存被select的view
 53                     oldSel = getChildAt(index);
 54                 }
 55 
 56                 // Remember the previous first child
 57                 oldFirst = getChildAt(0);    //保存第一个子view
 58 
 59                 if (mNextSelectedPosition >= 0) {
 60                     delta = mNextSelectedPosition - mSelectedPosition;
 61                 }
 62 
 63                 // Caution: newSel might be null
 64                 newSel = getChildAt(index + delta);
 65             }
 66 
 67 
 68             boolean dataChanged = mDataChanged;
 69             if (dataChanged) {
 70                 handleDataChanged();
 71             }
 72 
 73             // Handle the empty set by removing all views that are visible
 74             // and calling it a day
 75             if (mItemCount == 0) {    //如果没有item直接返回
 76                 resetList();
 77                 invokeOnItemScrollListener();
 78                 return;
 79             } else if (mItemCount != mAdapter.getCount()) {
 80                 throw new IllegalStateException("The content of the adapter has changed but "
 81                         + "ListView did not receive a notification. Make sure the content of "
 82                         + "your adapter is not modified from a background thread, but only "
 83                         + "from the UI thread. [in ListView(" + getId() + ", " + getClass() 
 84                         + ") with Adapter(" + mAdapter.getClass() + ")]");
 85             }
 86 
 87             setSelectedPositionInt(mNextSelectedPosition);
 88 
 89             // Pull all children into the RecycleBin.
 90             // These views will be reused if possible
 91             final int firstPosition = mFirstPosition;
 92             final RecycleBin recycleBin = mRecycler;
 93 
 94             // reset the focus restoration
 95             View focusLayoutRestoreDirectChild = null;
 96 
 97 
 98             // Don't put header or footer views into the Recycler. Those are
 99             // already cached in mHeaderViews;
100             if (dataChanged) {    //如果数据改变了,把所有的子view放进recycleBin的Scrapview中(不包括foot和head)
101                 for (int i = 0; i < childCount; i++) {
102                     recycleBin.addScrapView(getChildAt(i));
103                     if (ViewDebug.TRACE_RECYCLER) {
104                         ViewDebug.trace(getChildAt(i),
105                                 ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, index, i);
106                     }
107                 }
108             } else {    //如果没有改变,把所有子view,放进recycleBin的AcitveView(不包括foot和head)
109                 recycleBin.fillActiveViews(childCount, firstPosition);
110             }
111 
112             // take focus back to us temporarily to avoid the eventual
113             // call to clear focus when removing the focused child below
114             // from messing things up when ViewRoot assigns focus back
115             // to someone else
116             final View focusedChild = getFocusedChild();
117             if (focusedChild != null) {
118                 // TODO: in some cases focusedChild.getParent() == null
119 
120                 // we can remember the focused view to restore after relayout if the
121                 // data hasn't changed, or if the focused position is a header or footer
122                 if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
123                     focusLayoutRestoreDirectChild = focusedChild;
124                     // remember the specific view that had focus
125                     focusLayoutRestoreView = findFocus();
126                     if (focusLayoutRestoreView != null) {
127                         // tell it we are going to mess with it
128                         focusLayoutRestoreView.onStartTemporaryDetach();
129                     }
130                 }
131                 requestFocus();
132             }
133 
134             // Clear out old views
135             detachAllViewsFromParent();//清楚掉Listview中所有旧的view,等待attach新的view
136 
137             //根据mLayoutMode不同,使用不同的布局item view的方式
138             switch (mLayoutMode) {
139             case LAYOUT_SET_SELECTION:
140                 if (newSel != null) {
141                     sel = fillFromSelection(newSel.getTop(), childrenTop, childrenBottom);
142                 } else {
143                     sel = fillFromMiddle(childrenTop, childrenBottom);
144                 }
145                 break;
146             case LAYOUT_SYNC:
147                 sel = fillSpecific(mSyncPosition, mSpecificTop);
148                 break;
149             case LAYOUT_FORCE_BOTTOM:
150                 sel = fillUp(mItemCount - 1, childrenBottom);
151                 adjustViewsUpOrDown();
152                 break;
153             case LAYOUT_FORCE_TOP:
154                 mFirstPosition = 0;
155                 sel = fillFromTop(childrenTop);
156                 adjustViewsUpOrDown();
157                 break;
158             case LAYOUT_SPECIFIC:
159                 sel = fillSpecific(reconcileSelectedPosition(), mSpecificTop);
160                 break;
161             case LAYOUT_MOVE_SELECTION:
162                 sel = moveSelection(oldSel, newSel, delta, childrenTop, childrenBottom);
163                 break;
164             default:
165                 if (childCount == 0) {
166                     if (!mStackFromBottom) {
167                         final int position = lookForSelectablePosition(0, true);
168                         setSelectedPositionInt(position);
169                         sel = fillFromTop(childrenTop);
170                     } else {
171                         final int position = lookForSelectablePosition(mItemCount - 1, false);
172                         setSelectedPositionInt(position);
173                         sel = fillUp(mItemCount - 1, childrenBottom);
174                     }
175                 } else {
176                     if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) {
177                         sel = fillSpecific(mSelectedPosition,
178                                 oldSel == null ? childrenTop : oldSel.getTop());
179                     } else if (mFirstPosition < mItemCount) {
180                         sel = fillSpecific(mFirstPosition,
181                                 oldFirst == null ? childrenTop : oldFirst.getTop());
182                     } else {
183                         sel = fillSpecific(0, childrenTop);
184                     }
185                 }
186                 break;
187             }
188 
189             // Flush any cached views that did not get reused above
190             recycleBin.scrapActiveViews();    //清除掉recycleBin在上面没有被重用的缓存views
191 
192             if (sel != null) {
193                 // the current selected item should get focus if items
194                 // are focusable
195                 if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
196                     final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
197                             focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
198                     if (!focusWasTaken) {
199                         // selected item didn't take focus, fine, but still want
200                         // to make sure something else outside of the selected view
201                         // has focus
202                         final View focused = getFocusedChild();
203                         if (focused != null) {
204                             focused.clearFocus();
205                         }
206                         positionSelector(sel);
207                     } else {
208                         sel.setSelected(false);
209                         mSelectorRect.setEmpty();
210                     }
211                 } else {
212                     positionSelector(sel);
213                 }
214                 mSelectedTop = sel.getTop();
215             } else {
216                 if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
217                     View child = getChildAt(mMotionPosition - mFirstPosition);
218                     if (child != null) positionSelector(child);
219                 } else {
220                     mSelectedTop = 0;
221                     mSelectorRect.setEmpty();
222                 }
223 
224                 // even if there is not selected position, we may need to restore
225                 // focus (i.e. something focusable in touch mode)
226                 if (hasFocus() && focusLayoutRestoreView != null) {
227                     focusLayoutRestoreView.requestFocus();
228                 }
229             }
230 
231             // tell focus view we are done mucking with it, if it is still in
232             // our view hierarchy.
233             if (focusLayoutRestoreView != null
234                     && focusLayoutRestoreView.getWindowToken() != null) {
235                 focusLayoutRestoreView.onFinishTemporaryDetach();
236             }
237             
238             mLayoutMode = LAYOUT_NORMAL;
239             mDataChanged = false;
240             mNeedSync = false;
241             setNextSelectedPositionInt(mSelectedPosition);
242 
243             updateScrollIndicators();
244 
245             if (mItemCount > 0) {
246                 checkSelectionChanged();
247             }
248 
249             invokeOnItemScrollListener();
250         } finally {
251             if (!blockLayoutRequests) {
252                 mBlockLayoutRequests = false;
253             }
254         }
255     }

layoutChildren会根据mLayoutMode的值选用不同的方式返回一个item view,选择其中一种去介绍,这里选择fillDown,其他的其实差不多,大家可以自行研究:

fillDown()

fillDown
 1         /**
 2      * Fills the list from pos down to the end of the list view.从上往下渲染Listview
 3      *
 4      * @param pos The first position to put in the list
 5      *
 6      * @param nextTop The location where the top of the item associated with pos
 7      *        should be drawn
 8      *
 9      * @return The view that is currently selected, if it happens to be in the
10      *         range that we draw.
11      */
12     private View fillDown(int pos, int nextTop) {
13         View selectedView = null;
14 
15         int end = (mBottom - mTop) - mListPadding.bottom;
16 
17         while (nextTop < end && pos < mItemCount) {
18             // is this the selected item?
19             boolean selected = pos == mSelectedPosition;
20             View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);    //通过makeAndAddView返回一个view
21 
22             nextTop = child.getBottom() + mDividerHeight;
23             if (selected) {
24                 selectedView = child;
25             }
26             pos++;
27         }
28 
29         return selectedView;
30     }

其实fillDown中极其简单,就是调用了一下makeAndAddView返回一个view,makeAndAddView实现的是得到一个view并把它添加到Listview中去

那么,接下来介绍makeAndAddView()方法,它的作用是获得每一个item view对象,即Listview的每一行数据

makeAndAddView
 1         /**
 2      * Obtain the view and add it to our list of children. The view can be made
 3      * fresh, converted from an unused view, or used as is if it was in the
 4      * recycle bin.获得view并把它们添加到存放子view的list中去。这个view可以是新构造出来的,可以通过从没有用过的view转过来(翻译不怎么合适)或者如果recycle bin中存在可以重用view
 5      *
 6      * @param position Logical position in the list
 7      * @param y Top or bottom edge of the view to add
 8      * @param flow If flow is true, align top edge to y. If false, align bottom
 9      *        edge to y.
10      * @param childrenLeft Left edge where children should be positioned
11      * @param selected Is this position selected?
12      * @return View that was added
13      */
14     private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
15             boolean selected) {
16         View child;
17 
18         //mDataChanged表示的是最后一次layout是否有“数据”更新,数据的意思包含AdapterDataSetObserver中监测的数据、size的变化、view的重绘和重建、click,checked事件的发生。
19         //只有layoutChildren后mDataChanged会置为false
20         if (!mDataChanged) {    
21             // Try to use an exsiting view for this position
22             child = mRecycler.getActiveView(position);//从mRecycler中获取一个当前活动的view来重用,mRecycler(AbsListView.RecycleBin对象)会后面介绍
23             if (child != null) {
24                 if (ViewDebug.TRACE_RECYCLER) {//debug时候跟踪输出信息(可忽略)
25                     ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
26                             position, getChildCount());
27                 }
28 
29                 // Found it -- we're using an existing child
30                 // This just needs to be positioned
31                 setupChild(child, position, y, flow, childrenLeft, selected, true);//添加该view到Listview中去,这里只需要找到位置而不需要measure  view的大小
32 
33                 return child;
34             }
35         }
36 
37         // Make a new view for this position, or convert an unused view if possible
38         child = obtainView(position, mIsScrap);//获取一个新的view或者从一个没有用的view转换过来,AbsListview的方法,涉及到Listview中item的重用,和RecycleBin相关
39 
40         // This needs to be positioned and measured
41         setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);//添加该view到Listview中去,这里只需要找到位置而且需要measure  view的大小
42 
43         return child;//返回该view对象
44     }

 

在makeAndAddView中,我们看到mRecycler对象,究竟该对象是什么,为什么可以从这个对象中获得一个item view去渲染Listview本身?

mRecycler是一个RecycleBin对象,从mRecycler的命名可以略知一二,它是一个回收器,回收什么?既然可以从里面获得view,那毫无疑问,回收的肯定有item view,那进一步看看该对象,它是Listview的item重用机制的重要对象,它是一个AbsListview的一个内部类:

  1 RecycleBin 
  2      /**
  3      * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of
  4      * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the
  5      * start of a layout. By construction, they are displaying current information. At the end of
  6      * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that
  7      * could potentially be used by the adapter to avoid allocating views unnecessarily.
  8      *
  9      *解释:Recyclebin实现跨layout的view重用。在RecycleBin中,存储两种不同的view,ActiveViews 和 ScrapViews。
 10      *ActiveViews保存布局开始时屏幕中显示的views。在布局结束时,所有的ActiveViews变成ScrapViews。
 11      *ScrapViews是那些可以被adapter使用,避免重新分配新的view的旧views。
 12      *
 13      * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
 14      * @see android.widget.AbsListView.RecyclerListener
 15      */
 16     class RecycleBin {
 17         private RecyclerListener mRecyclerListener;
 18 
 19         /**
 20          * The position of the first view stored in mActiveViews.
 21          */
 22         private int mFirstActivePosition;
 23 
 24         /**
 25          * Views that were on screen at the start of layout. This array is populated at the start of
 26          * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews.
 27          * Views in mActiveViews represent a contiguous range of Views, with position of the first
 28          * view store in mFirstActivePosition.
 29          *
 30          *mActiveViews 包含当前屏幕中的item views,它是一个数组,在一开始布局时会被填充,在布局结束时,它里面的view
 31          *会移动到mScrapViews中。在mActiveViews 中的views代表连续范围内的views。
 32          */
 33         private View[] mActiveViews = new View[0];
 34 
 35         /**
 36          * Unsorted views that can be used by the adapter as a convert view.
 37          */
 38         private ArrayList<View>[] mScrapViews;    //存储scrapviews的地方,一个ArrayList数组,一个元素对应一种类型的scrapviews,它可以被adapter作为一个convert view使用
 39 
 40         private int mViewTypeCount;    //item view类型的总数(listview可以包含不同类型的item views)
 41 
 42         private ArrayList<View> mCurrentScrap;
 43 
 44         public void setViewTypeCount(int viewTypeCount) {
 45             if (viewTypeCount < 1) {
 46                 throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
 47             }
 48             //noinspection unchecked
 49             //根据view类型数量,设置scrapviews的存储结构,每一种类型的scrapviews用一个ArrayList去存储,多个类型狗就有多个ArrayList
 50             ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];    
 51             for (int i = 0; i < viewTypeCount; i++) {
 52                 scrapViews[i] = new ArrayList<View>();
 53             }
 54             mViewTypeCount = viewTypeCount;
 55             mCurrentScrap = scrapViews[0];
 56             mScrapViews = scrapViews;
 57         }
 58         
 59         //设置mCurrentScrap中存储的views在下一次layout时必须重新layout
 60         public void markChildrenDirty() {
 61             if (mViewTypeCount == 1) {    //如果只有一种类型的item
 62                 final ArrayList<View> scrap = mCurrentScrap;
 63                 final int scrapCount = scrap.size();
 64                 for (int i = 0; i < scrapCount; i++) {
 65                     scrap.get(i).forceLayout();
 66                 }
 67             } else {
 68                 final int typeCount = mViewTypeCount;
 69                 for (int i = 0; i < typeCount; i++) {
 70                     final ArrayList<View> scrap = mScrapViews[i];
 71                     final int scrapCount = scrap.size();
 72                     for (int j = 0; j < scrapCount; j++) {
 73                         scrap.get(j).forceLayout();
 74                     }
 75                 }
 76             }
 77         }
 78 
 79         public boolean shouldRecycleViewType(int viewType) {
 80             return viewType >= 0;
 81         }
 82 
 83         /**
 84          * Clears the scrap heap.清楚掉mScrapViews中的views
 85          */
 86         void clear() {
 87             if (mViewTypeCount == 1) {
 88                 final ArrayList<View> scrap = mCurrentScrap;
 89                 final int scrapCount = scrap.size();
 90                 for (int i = 0; i < scrapCount; i++) {
 91                     removeDetachedView(scrap.remove(scrapCount - 1 - i), false);
 92                 }
 93             } else {
 94                 final int typeCount = mViewTypeCount;
 95                 for (int i = 0; i < typeCount; i++) {
 96                     final ArrayList<View> scrap = mScrapViews[i];
 97                     final int scrapCount = scrap.size();
 98                     for (int j = 0; j < scrapCount; j++) {
 99                         removeDetachedView(scrap.remove(scrapCount - 1 - j), false);
100                     }
101                 }
102             }
103         }
104 
105         /**
106          * Fill ActiveViews with all of the children of the AbsListView.把AbsListview中的所以子view添加到ActiveViews中
107          *
108          * @param childCount The minimum number of views mActiveViews should hold
109          * @param firstActivePosition The position of the first view that will be stored in
110          *        mActiveViews
111          */
112         void fillActiveViews(int childCount, int firstActivePosition) {
113             if (mActiveViews.length < childCount) {
114                 mActiveViews = new View[childCount];
115             }
116             mFirstActivePosition = firstActivePosition;
117 
118             final View[] activeViews = mActiveViews;
119             for (int i = 0; i < childCount; i++) {
120                 View child = getChildAt(i);//这里注意是把当前listview的item作为active view,也就是当前的active view会作为重用的view,第一次加载的时候,没有子view,所以第一次时,listview的所有view都不是重用的
121                 AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
122                 // Don't put header or footer views into the scrap heap
123                 if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
124                     // Note:  We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views.
125                     //        However, we will NOT place them into scrap views.
126                     activeViews[i] = child;
127                 }
128             }
129         }
130 
131         /**
132          * Get the view corresponding to the specified position. The view will be removed from
133          * mActiveViews if it is found.从ActiveViews指定位置获得一个view,获得后,这个view会从ActiveViews中去掉
134          *
135          * @param position The position to look up in mActiveViews
136          * @return The view if it is found, null otherwise
137          */
138         View getActiveView(int position) {
139             int index = position - mFirstActivePosition;
140             final View[] activeViews = mActiveViews;
141             if (index >=0 && index < activeViews.length) {
142                 final View match = activeViews[index];
143                 activeViews[index] = null;
144                 return match;
145             }
146             return null;
147         }
148 
149         /**
150          * @return A view from the ScrapViews collection. These are unordered.从ScrapViews中获得一个view
151          */
152         View getScrapView(int position) {
153             ArrayList<View> scrapViews;
154             if (mViewTypeCount == 1) {
155                 scrapViews = mCurrentScrap;
156                 int size = scrapViews.size();
157                 if (size > 0) {
158                     return scrapViews.remove(size - 1);
159                 } else {
160                     return null;
161                 }
162             } else {
163                 int whichScrap = mAdapter.getItemViewType(position);
164                 if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
165                     scrapViews = mScrapViews[whichScrap];
166                     int size = scrapViews.size();
167                     if (size > 0) {
168                         return scrapViews.remove(size - 1);
169                     }
170                 }
171             }
172             return null;
173         }
174 
175         /**
176          * Put a view into the ScapViews list. These views are unordered.把一个view存储到ScrapViews中
177          *
178          * @param scrap The view to add
179          */
180         void addScrapView(View scrap) {
181             AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
182             if (lp == null) {
183                 return;
184             }
185 
186             // Don't put header or footer views or views that should be ignored
187             // into the scrap heap
188             int viewType = lp.viewType;
189             if (!shouldRecycleViewType(viewType)) {
190                 if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
191                     removeDetachedView(scrap, false);
192                 }
193                 return;
194             }
195 
196             if (mViewTypeCount == 1) {
197                 scrap.dispatchStartTemporaryDetach();
198                 mCurrentScrap.add(scrap);
199             } else {
200                 scrap.dispatchStartTemporaryDetach();
201                 mScrapViews[viewType].add(scrap);
202             }
203 
204             if (mRecyclerListener != null) {
205                 mRecyclerListener.onMovedToScrapHeap(scrap);
206             }
207         }
208 
209         /**
210          * Move all views remaining in mActiveViews to mScrapViews.把所以的activeviews变成scrapviews
211          */
212         void scrapActiveViews() {
213             final View[] activeViews = mActiveViews;
214             final boolean hasListener = mRecyclerListener != null;
215             final boolean multipleScraps = mViewTypeCount > 1;
216 
217             ArrayList<View> scrapViews = mCurrentScrap;
218             final int count = activeViews.length;
219             for (int i = count - 1; i >= 0; i--) {
220                 final View victim = activeViews[i];
221                 if (victim != null) {
222                     int whichScrap = ((AbsListView.LayoutParams) victim.getLayoutParams()).viewType;
223 
224                     activeViews[i] = null;
225 
226                     if (!shouldRecycleViewType(whichScrap)) {
227                         // Do not move views that should be ignored
228                         if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
229                             removeDetachedView(victim, false);
230                         }
231                         continue;
232                     }
233 
234                     if (multipleScraps) {
235                         scrapViews = mScrapViews[whichScrap];
236                     }
237                     victim.dispatchStartTemporaryDetach();
238                     scrapViews.add(victim);
239 
240                     if (hasListener) {
241                         mRecyclerListener.onMovedToScrapHeap(victim);
242                     }
243 
244                     if (ViewDebug.TRACE_RECYCLER) {
245                         ViewDebug.trace(victim,
246                                 ViewDebug.RecyclerTraceType.MOVE_FROM_ACTIVE_TO_SCRAP_HEAP,
247                                 mFirstActivePosition + i, -1);
248                     }
249                 }
250             }
251 
252             pruneScrapViews();
253         }
254 
255         /**
256          * Makes sure that the size of mScrapViews does not exceed the size of mActiveViews.
257          * (This can happen if an adapter does not recycle its views).保证mScrapViews中view数量始终小于等于mActiveViews中view的数量
258          */
259         private void pruneScrapViews() {
260             final int maxViews = mActiveViews.length;
261             final int viewTypeCount = mViewTypeCount;
262             final ArrayList<View>[] scrapViews = mScrapViews;
263             for (int i = 0; i < viewTypeCount; ++i) {
264                 final ArrayList<View> scrapPile = scrapViews[i];
265                 int size = scrapPile.size();
266                 final int extras = size - maxViews;
267                 size--;
268                 for (int j = 0; j < extras; j++) {
269                     removeDetachedView(scrapPile.remove(size--), false);
270                 }
271             }
272         }
273 
274         /**
275          * Puts all views in the scrap heap into the supplied list.把所有的mScrapViews发到自己传进去的List中
276          */
277         void reclaimScrapViews(List<View> views) {
278             if (mViewTypeCount == 1) {
279                 views.addAll(mCurrentScrap);
280             } else {
281                 final int viewTypeCount = mViewTypeCount;
282                 final ArrayList<View>[] scrapViews = mScrapViews;
283                 for (int i = 0; i < viewTypeCount; ++i) {
284                     final ArrayList<View> scrapPile = scrapViews[i];
285                     views.addAll(scrapPile);
286                 }
287             }
288         }
289 
290         /**
291          * Updates the cache color hint of all known views.
292          *
293          * @param color The new cache color hint.
294          */
295         void setCacheColorHint(int color) {
296             if (mViewTypeCount == 1) {
297                 final ArrayList<View> scrap = mCurrentScrap;
298                 final int scrapCount = scrap.size();
299                 for (int i = 0; i < scrapCount; i++) {
300                     scrap.get(i).setDrawingCacheBackgroundColor(color);
301                 }
302             } else {
303                 final int typeCount = mViewTypeCount;
304                 for (int i = 0; i < typeCount; i++) {
305                     final ArrayList<View> scrap = mScrapViews[i];
306                     final int scrapCount = scrap.size();
307                     for (int j = 0; j < scrapCount; j++) {
308                         scrap.get(i).setDrawingCacheBackgroundColor(color);
309                     }
310                 }
311             }
312             // Just in case this is called during a layout pass
313             final View[] activeViews = mActiveViews;
314             final int count = activeViews.length;
315             for (int i = 0; i < count; ++i) {
316                 final View victim = activeViews[i];
317                 if (victim != null) {
318                     victim.setDrawingCacheBackgroundColor(color);
319                 }
320             }
321         }
322     }
RecycleBin

 

这个类很简单,就是一些相关的add、get之类的方法。ListView是通过该类的对象实例mRecycler缓存view,当滚动Listview时,即不可视view变成可视时,Listview就有可能使用mRecycler里面的view实现重用,而不需要重新创建一个view,避免资源的浪费。

 

回头看一下makeAndAddView两种获得view的方式,一个是mRecycler.getActiveView(position),一个是obtainView(position, mIsScrap),前者已经介绍,看完RecycleBin就知道,它就是重用了ActiveViews中的view,后者是怎么样的呢?

obtainView
 1     --->AbsListview.java
 2         /**
 3      * Get a view and have it show the data associated with the specified
 4      * position. This is called when we have already discovered that the view is
 5      * not available for reuse in the recycle bin. The only choices left are
 6      * converting an old view or making a new one.这个方法会什么时候调用?就是当view不能通过recycle bin(其实就是mRecycler对象)
 7      *重用的时候调用。那么这个时候的唯一方法就是转换一个旧的view或者创建一个新的view
 8      *
 9      * @param position The position to display
10      * @param isScrap Array of at least 1 boolean, the first entry will become true if
11      *                the returned view was taken from the scrap heap, false if otherwise.如果返回的view是从scrap heap中获得的,isScrap第一个元素将会是true,如果是自己创建的新view,它就是true
12      * 
13      * @return A view displaying the data associated with the specified position
14      */
15     View obtainView(int position, boolean[] isScrap) {
16         isScrap[0] = false;
17         View scrapView;
18 
19         scrapView = mRecycler.getScrapView(position);    //在Scrapview中获取view
20 
21         View child;
22         if (scrapView != null) {    //如果有这个view,我们就通过old view去转换
23             if (ViewDebug.TRACE_RECYCLER) {
24                 ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.RECYCLE_FROM_SCRAP_HEAP,
25                         position, -1);
26             }
27             
28             //通过把scrapView作为参数传进去getView方法,这样就可以实现通过mAdapter转换成一个新view(推荐mAdapter要使用holder的那种写法)
29             child = mAdapter.getView(position, scrapView, this);    
30 
31             if (ViewDebug.TRACE_RECYCLER) {
32                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.BIND_VIEW,
33                         position, getChildCount());
34             }
35 
36             if (child != scrapView) {    //如果你的getView方法返回的view是自己重新创建的view,那么scrapView就与它不是相等的
37                 mRecycler.addScrapView(scrapView);    //mRecycler存储这个scrapView
38                 if (mCacheColorHint != 0) {
39                     child.setDrawingCacheBackgroundColor(mCacheColorHint);
40                 }
41                 if (ViewDebug.TRACE_RECYCLER) {
42                     ViewDebug.trace(scrapView, ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
43                             position, -1);
44                 }
45             } else {
46                 isScrap[0] = true;    //设置isScrap的第一个元素为true,表明是通过old view转换获取view的
47                 child.dispatchFinishTemporaryDetach();
48             }
49         } else {
50             child = mAdapter.getView(position, null, this);    //第二个参数为null,那么只能创建一个新的view了
51             if (mCacheColorHint != 0) {
52                 child.setDrawingCacheBackgroundColor(mCacheColorHint);
53             }
54             if (ViewDebug.TRACE_RECYCLER) {
55                 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.NEW_VIEW,
56                         position, getChildCount());
57             }
58         }
59 
60         return child;
61     }

 

 介绍到这里,可以总结一下Listview是怎么重用item view的了:

如果数据没有变化,那么直接从ActiveViews中取到view,然后去布局,ActiveViews保存的就是当前可视的views,这种情况下相当于,缓存下来的view还是被布局会原来的位置,什么也没有变化;

如果数据变化了,那么可能会从SrapViews中去到view去完成布局,ScrapVews其实就缓存的是上一次可视的views,这种情况的好处是,即使数据已经变化了,但依然不需要去创建一个新的view,避免耗费移动设备可怜少的资源。

这里说的数据变化其中的“数据”并不是仅仅是adapter中的真实数据,而是包括:AdapterDataSetObserver中监测的数据、size的变化、view的重绘和重建、click,checked等事件的发生。

posted on 2012-12-11 21:57  wacao  阅读(1836)  评论(1编辑  收藏  举报

导航