您的位置:首页 > 其它

Listview部分源码分析

2016-03-08 13:43 387 查看
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等事件的发生。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: