先来看ListView类中的makeAndAddView方法:
1 /** 2 * 获取视图填充到列表的item中去,视图可以是从未使用过的视图转换过来,也可以是从回收站复用的视图。 3 * 在该方法中,先查找是否有可重用视图,如果有,使用可重用视图。 4 * 然后通过obtainView方法获取一个view(有可能是从未使用视图转换过来 5 * (obtainView方法是在AbsListView方法中定义)),再重新测量和定位View。 6 * Obtain the view and add it to our list of children. The view can be made 7 * fresh, converted from an unused view, or used as is if it was in the 8 * recycle bin. 9 * 10 * @param position Logical position in the list 11 * @param y Top or bottom edge of the view to add 12 * @param flow If flow is true, align top edge to y. If false, align bottom 13 * edge to y. 14 * @param childrenLeft Left edge where children should be positioned 15 * @param selected Is this position selected? 16 * @return View that was added 17 */ 18 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, 19 boolean selected) { 20 View child; 21 22 23 if (!mDataChanged) { 24 // Try to use an existing view for this position 25 child = mRecycler.getActiveView(position); 26 if (child != null) { 27 if (ViewDebug.TRACE_RECYCLER) { 28 ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP, 29 position, getChildCount()); 30 } 31 32 // Found it -- we‘re using an existing child 33 // This just needs to be positioned 34 setupChild(child, position, y, flow, childrenLeft, selected, true); 35 36 return child; 37 } 38 } 39 40 // Make a new view for this position, or convert an unused view if possible 41 child = obtainView(position, mIsScrap); 42 43 // This needs to be positioned and measured 44 setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); 45 46 return child; 47 }
第41行调用了obtainView方法,该方法的实现是在package android.widget;的AbsListView类中
1 /** 2 * Get a view and have it show the data associated with the specified 3 * position. This is called when we have already discovered that the view is 4 * not available for reuse in the recycle bin. The only choices left are 5 * converting an old view or making a new one. 6 * 7 * @param position The position to display 8 * @param isScrap Array of at least 1 boolean, the first entry will become true if 9 * the returned view was taken from the scrap heap, false if otherwise. 10 * 11 * @return A view displaying the data associated with the specified position 12 */ 13 View obtainView(int position, boolean[] isScrap) { 14 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); 15 16 isScrap[0] = false; 17 View scrapView; 18 19 scrapView = mRecycler.getTransientStateView(position); 20 if (scrapView == null) { 21 // 从回收站回收一个View 22 scrapView = mRecycler.getScrapView(position); 23 } 24 25 View child; 26 if (scrapView != null) { 27 // 这里调用了getView!注意,第二个参数也就是convertView传 入的是刚才从回收站中回收的View(如果有的话) 28 child = mAdapter.getView(position, scrapView, this); 29 30 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 31 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 32 } 33 34 if (child != scrapView) { 35 mRecycler.addScrapView(scrapView, position); 36 if (mCacheColorHint != 0) { 37 child.setDrawingCacheBackgroundColor(mCacheColorHint); 38 } 39 } else { 40 isScrap[0] = true; 41 42 // Clear any system-managed transient state so that we can 43 // recycle this view and bind it to different data. 44 if (child.isAccessibilityFocused()) { 45 child.clearAccessibilityFocus(); 46 } 47 48 child.dispatchFinishTemporaryDetach(); 49 } 50 } else { 51 child = mAdapter.getView(position, null, this); 52 53 if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 54 child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 55 } 56 57 if (mCacheColorHint != 0) { 58 child.setDrawingCacheBackgroundColor(mCacheColorHint); 59 } 60 } 61 62 if (mAdapterHasStableIds) { 63 final ViewGroup.LayoutParams vlp = child.getLayoutParams(); 64 LayoutParams lp; 65 if (vlp == null) { 66 lp = (LayoutParams) generateDefaultLayoutParams(); 67 } else if (!checkLayoutParams(vlp)) { 68 lp = (LayoutParams) generateLayoutParams(vlp); 69 } else { 70 lp = (LayoutParams) vlp; 71 } 72 lp.itemId = mAdapter.getItemId(position); 73 child.setLayoutParams(lp); 74 } 75 76 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 77 if (mAccessibilityDelegate == null) { 78 mAccessibilityDelegate = new ListItemAccessibilityDelegate(); 79 } 80 if (child.getAccessibilityDelegate() == null) { 81 child.setAccessibilityDelegate(mAccessibilityDelegate); 82 } 83 } 84 85 Trace.traceEnd(Trace.TRACE_TAG_VIEW); 86 87 return child; 88 }
第28行调用了getView!根据Java多态的特性,实际执行的getView将会是我们自定义BaseAdapter中的那个getView方法。
好,现在虽然找到getView的直接调用者了,问题来了,何时去触发makeAndAddView并调用getView呢?
我们首先来看ListView中的fillDown:
1 /** 2 填充从pos到list底部所有的item。里面调用到了makeAndAddView方 法: 3 * Fills the list from pos down to the end of the list view. 4 * 5 * @param pos The first position to put in the list 6 * 7 * @param nextTop The location where the top of the item associated with pos 8 * should be drawn 9 * 10 * @return The view that is currently selected, if it happens to be in the 11 * range that we draw. 12 */ 13 private View fillDown(int pos, int nextTop) { 14 View selectedView = null; 15 16 int end = (mBottom - mTop); 17 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 18 end -= mListPadding.bottom; 19 } 20 21 while (nextTop < end && pos < mItemCount) { 22 // is this the selected item? 23 boolean selected = pos == mSelectedPosition; 24 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); 25 26 nextTop = child.getBottom() + mDividerHeight; 27 if (selected) { 28 selectedView = child; 29 } 30 pos++; 31 } 32 33 return selectedView; 34 }
有fillDown自然就有fillUp:
1 /** 2 * Fills the list from pos up to the top of the list view. 3 * 4 * @param pos The first position to put in the list 5 * 6 * @param nextBottom The location where the bottom of the item associated 7 * with pos should be drawn 8 * 9 * @return The view that is currently selected 10 */ 11 private View fillUp(int pos, int nextBottom) { 12 View selectedView = null; 13 14 int end = 0; 15 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { 16 end = mListPadding.top; 17 } 18 19 while (nextBottom > end && pos >= 0) { 20 // is this the selected item? 21 boolean selected = pos == mSelectedPosition; 22 View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected); 23 nextBottom = child.getTop() - mDividerHeight; 24 if (selected) { 25 selectedView = child; 26 } 27 pos--; 28 } 29 30 mFirstPosition = pos + 1; 31 32 return selectedView; 33 }
还有fillFromTop、fillFromMiddle、fillAboveAndBelow、fillFromSelection等,这些方法都是用来填充ListView的,区别是参照的起始位置不同而已。
好了,现在填充的方法有了,那么谁来触发这些方法呢?
通过查找ListView源码,发现刚才的那些填充方法在layoutChildren()中基本上都被调用到,而layoutChildren的调用者是onFocusChanged、setSelectionInt、父类AbsListView中的onTouchMove等,说明当ListView的焦点发生变化时、选中某一项、或者滑动ListView时都会触发ListView的layoutChildren()重新装载应当显示的item。
1 @Override 2 protected void layoutChildren() { 3 final boolean blockLayoutRequests = mBlockLayoutRequests; 4 if (blockLayoutRequests) { 5 return; 6 } 7 8 mBlockLayoutRequests = true; 9 10 try { 11 super.layoutChildren(); 12 13 invalidate();
我们仔细阅读layoutChildren()方法,发现在第13行,调用了invalidate();,也就是重绘了ListView。
到此为止我们已经很清楚getView的调用时机了,根据掌握的知识点,我们很自然能想到,当初始化一个ListView时,getView的调用也是避免不了的。这是因为ListView在初始化时肯定会绑定一个adapter,即调用语句listview.setAdapter(adapter),我们看一下setAdapter的源码:
1 @Override 2 public void setAdapter(ListAdapter adapter) { 3 if (mAdapter != null && mDataSetObserver != null) { 4 mAdapter.unregisterDataSetObserver(mDataSetObserver); 5 } 6 7 resetList(); 8 mRecycler.clear(); 9 10 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { 11 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); 12 } else { 13 mAdapter = adapter; 14 } 15 16 mOldSelectedPosition = INVALID_POSITION; 17 mOldSelectedRowId = INVALID_ROW_ID; 18 19 // AbsListView#setAdapter will update choice mode states. 20 super.setAdapter(adapter); 21 22 if (mAdapter != null) { 23 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); 24 mOldItemCount = mItemCount; 25 mItemCount = mAdapter.getCount(); 26 checkFocus(); 27 28 mDataSetObserver = new AdapterDataSetObserver(); 29 mAdapter.registerDataSetObserver(mDataSetObserver); 30 31 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); 32 33 int position; 34 if (mStackFromBottom) { 35 position = lookForSelectablePosition(mItemCount - 1, false); 36 } else { 37 position = lookForSelectablePosition(0, true); 38 } 39 setSelectedPositionInt(position); 40 setNextSelectedPositionInt(position); 41 42 if (mItemCount == 0) { 43 // Nothing selected 44 checkSelectionChanged(); 45 } 46 } else { 47 mAreAllItemsSelectable = true; 48 checkFocus(); 49 // Nothing selected 50 checkSelectionChanged(); 51 } 52 53 requestLayout(); 54 }
通读setAdapter源码,我们发现其中并未出现生成新子视图,即调用mAdapter.getView的语句或方法,说明此时ListView并未包含子视图。那么疑问来了,ListView是如何在初始化的时候生成子视图的?往后看,我们发现在第53行调用了requestLayout请求布局重绘,我们知道requestLayout最终会去调用ListView或父类的onMeasure、onLayout、onDraw方法,因此我们猜测会不会是在onMeasure、onLayout、onDraw某个方法中生成了子视图?
答案是肯定的,ListView.onMeasure中有AbsListView.obtainView的调用语句(这里还需深入研究,因为涉及到ListView高度探测),也即生成了新的子视图。此外,ListVIew.onLayout过程与普通视图的layout过程完全不同,该方法调用了layoutChildren();,即对ListView中的子视图进行重新布局,具体过程请参考《Android ListView初始化简单分析》一文。
由此说明调用requestLayout可以生成ListView的子视图,这里联想到adapter.notifyDataSetChanged也会调用requestLayout,从而能都实现ListView的刷新。
以上过程只是个人探索,并非绝对正确,如有差错敬请批评指正,谢谢。
参考文献: