Listview部分源码分析
Listview在android开发中算是最常用的几个控件之一,由于要应付各种不同的需求,甚至有时候是奇怪且独特的需要,使用Listview就总会遇到些奇怪的问题,但是其实都没有什么捷径,看源码是最好的办法。而与Listview相关的源码至少有一万行,涉及到AbsListview、AdapterView、ListAdapter等,这里对部分的源码做分析。下面由于代码数量比较多,只要着重看有中文注释的部分就可以了。
1.与数据adapter相关的
setAdapter()
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()
1 public void registerDataSetObserver(DataSetObserver observer) { 2 mDataSetObservable.registerObserver(observer); 3 }
它作用就是调用DataSetObservable的registerDataSetObserver,而该方法是在Observable定义的:
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时,干了什么?
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()方法:
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方法:
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()
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()
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()
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的每一行数据
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 }
这个类很简单,就是一些相关的add、get之类的方法。ListView是通过该类的对象实例mRecycler缓存view,当滚动Listview时,即不可视view变成可视时,Listview就有可能使用mRecycler里面的view实现重用,而不需要重新创建一个view,避免资源的浪费。
回头看一下makeAndAddView两种获得view的方式,一个是mRecycler.getActiveView(position),一个是obtainView(position, mIsScrap),前者已经介绍,看完RecycleBin就知道,它就是重用了ActiveViews中的view,后者是怎么样的呢?
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等事件的发生。