转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/44996879

在Android全部经常使用的原生控件其中。使用方法最复杂的应该就是ListView了,它专门用于处理那种内容元素许多。手机屏幕无法展示出全部内容的情况。

ListView能够使用列表的形式来展示内容,超出屏幕部分的内容仅仅须要通过手指滑动就能够移动到屏幕内了。

另外ListView另一个很奇妙的功能,我相信大家应该都体验过,即使在ListView中载入很许多的数据。比方达到成百上千条甚至许多其它,ListView都不会发生OOM或者崩溃,而且随着我们手指滑动来浏览许多其它数据时,程序所占用的内存居然都不会跟着增长。

那么ListView是怎么实现这么奇妙的功能的呢?当初我就抱着学习的心态花了很长时间把ListView的源代码通读了一遍,基本了解了它的工作原理,在感叹Google大神能够写出如此精妙代码的同一时候我也有所敬畏,由于ListView的代码量比較大,复杂度也很高,很难用文字表达清晰。于是我就放弃了把它写成一篇博客的想法。那么如今回忆起来这件事我已经肠子都悔青了。由于没过几个月时间我就把当初梳理清晰的源代码又忘的一干二净。于是如今我又又一次定下心来再次把ListView的源代码重读了一遍,那么这次我一定要把它写成一篇博客,分享给大家的同一时候也当成我自己的笔记吧。

首先我们先来看一下ListView的继承结构,例如以下图所看到的:

能够看到,ListView的继承结构还是相当复杂的,它是直接继承自的AbsListView。而AbsListView有两个子实现类,一个是ListView。另一个就是GridView。因此我们从这一点就能够猜出来。ListView和GridView在工作原理和实现上都是有许多共同点的。然后AbsListView又继承自AdapterView。AdapterView继承自ViewGroup,后面就是我们所熟知的了。先把ListView的继承结构了解一下。待会儿有助于我们更加清晰地分析代码。

Adapter的作用

Adapter相信大家都不会陌生。我们平时使用ListView的时候一定都会用到它。

那么话说回来大家有没有细致想过。为什么须要Adapter这个东西呢?总感觉正由于有了Adapter,ListView的使用变得要比其它控件复杂得多。那么这里我们就先来学习一下Adapter究竟起到了什么样的一个作用。

事实上说究竟,控件就是为了交互和展示数据用的。仅仅只是ListView更加特殊。它是为了展示许多许多数据用的,可是ListView仅仅承担交互和展示工作而已,至于这些数据来自哪里,ListView是不关心的。因此,我们能设想到的最基本的ListView工作模式就是要有一个ListView控件和一个数据源。

只是假设真的让ListView和数据源直接打交道的话。那ListView所要做的适配工作就很繁杂了。

由于数据源这个概念太模糊了,我们仅仅知道它包括了许多数据而已。至于这个数据源究竟是什么样类型。并没有严格的定义,有可能是数组,也有可能是集合,甚至有可能是数据库表中查询出来的游标。

所以说假设ListView真的去为每一种数据源都进行适配操作的话,一是扩展性会比較差,内置了几种适配就仅仅有几种适配,不能动态进行加入。二是超出了它本身应该负责的工作范围,不再是仅仅承担交互和展示工作就能够了,这样ListView就会变得比較臃肿。

那么显然Android开发团队是不会同意这样的事情发生的。于是就有了Adapter这样一个机制的出现。

顾名思义,Adapter是适配器的意思。它在ListView和数据源之间起到了一个桥梁的作用,ListView并不会直接和数据源打交道,而是会借助Adapter这个桥梁来去訪问真正的数据源。与之前不同的是,Adapter的接口都是统一的。因此ListView不用再去操心不论什么适配方面的问题。

而Adapter又是一个接口(interface),它能够去实现各种各样的子类,每一个子类都能通过自己的逻辑来去完毕特定的功能,以及与特定数据源的适配操作,比方说ArrayAdapter能够用于数组和List类型的数据源适配。SimpleCursorAdapter能够用于游标类型的数据源适配,这样就很巧妙地把数据源适配困难的问题解决掉了。而且还拥有相当不错的扩展性。简单的原理示意图例如以下所看到的:

当然Adapter的作用不仅仅仅仅有数据源适配这一点,另一个很很重要的方法也须要我们在Adapter其中去重写。就是getView()方法,这个在以下的文章中还会详细讲到。

RecycleBin机制

那么在開始分析ListView的源代码之前。另一个东西是我们提前须要了解的。就是RecycleBin机制,这个机制也是ListView能够实现成百上千条数据都不会OOM最重要的一个原因。事实上RecycleBin的代码并不多。仅仅有300行左右,它是写在AbsListView中的一个内部类。所以全部继承自AbsListView的子类。也就是ListView和GridView,都能够使用这个机制。那我们来看一下RecycleBin中的主要代码,例如以下所看到的:

  1. /**
  2. * The RecycleBin facilitates reuse of views across layouts. The RecycleBin
  3. * has two levels of storage: ActiveViews and ScrapViews. ActiveViews are
  4. * those views which were onscreen at the start of a layout. By
  5. * construction, they are displaying current information. At the end of
  6. * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews
  7. * are old views that could potentially be used by the adapter to avoid
  8. * allocating views unnecessarily.
  9. *
  10. * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener)
  11. * @see android.widget.AbsListView.RecyclerListener
  12. */
  13. class RecycleBin {
  14. private RecyclerListener mRecyclerListener;
  15.  
  16. /**
  17. * The position of the first view stored in mActiveViews.
  18. */
  19. private int mFirstActivePosition;
  20.  
  21. /**
  22. * Views that were on screen at the start of layout. This array is
  23. * populated at the start of layout, and at the end of layout all view
  24. * in mActiveViews are moved to mScrapViews. Views in mActiveViews
  25. * represent a contiguous range of Views, with position of the first
  26. * view store in mFirstActivePosition.
  27. */
  28. private View[] mActiveViews = new View[0];
  29.  
  30. /**
  31. * Unsorted views that can be used by the adapter as a convert view.
  32. */
  33. private ArrayList<View>[] mScrapViews;
  34.  
  35. private int mViewTypeCount;
  36.  
  37. private ArrayList<View> mCurrentScrap;
  38.  
  39. /**
  40. * Fill ActiveViews with all of the children of the AbsListView.
  41. *
  42. * @param childCount
  43. * The minimum number of views mActiveViews should hold
  44. * @param firstActivePosition
  45. * The position of the first view that will be stored in
  46. * mActiveViews
  47. */
  48. void fillActiveViews(int childCount, int firstActivePosition) {
  49. if (mActiveViews.length < childCount) {
  50. mActiveViews = new View[childCount];
  51. }
  52. mFirstActivePosition = firstActivePosition;
  53. final View[] activeViews = mActiveViews;
  54. for (int i = 0; i < childCount; i++) {
  55. View child = getChildAt(i);
  56. AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
  57. // Don't put header or footer views into the scrap heap
  58. if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
  59. // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in
  60. // active views.
  61. // However, we will NOT place them into scrap views.
  62. activeViews[i] = child;
  63. }
  64. }
  65. }
  66.  
  67. /**
  68. * Get the view corresponding to the specified position. The view will
  69. * be removed from mActiveViews if it is found.
  70. *
  71. * @param position
  72. * The position to look up in mActiveViews
  73. * @return The view if it is found, null otherwise
  74. */
  75. View getActiveView(int position) {
  76. int index = position - mFirstActivePosition;
  77. final View[] activeViews = mActiveViews;
  78. if (index >= 0 && index < activeViews.length) {
  79. final View match = activeViews[index];
  80. activeViews[index] = null;
  81. return match;
  82. }
  83. return null;
  84. }
  85.  
  86. /**
  87. * Put a view into the ScapViews list. These views are unordered.
  88. *
  89. * @param scrap
  90. * The view to add
  91. */
  92. void addScrapView(View scrap) {
  93. AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
  94. if (lp == null) {
  95. return;
  96. }
  97. // Don't put header or footer views or views that should be ignored
  98. // into the scrap heap
  99. int viewType = lp.viewType;
  100. if (!shouldRecycleViewType(viewType)) {
  101. if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
  102. removeDetachedView(scrap, false);
  103. }
  104. return;
  105. }
  106. if (mViewTypeCount == 1) {
  107. dispatchFinishTemporaryDetach(scrap);
  108. mCurrentScrap.add(scrap);
  109. } else {
  110. dispatchFinishTemporaryDetach(scrap);
  111. mScrapViews[viewType].add(scrap);
  112. }
  113.  
  114. if (mRecyclerListener != null) {
  115. mRecyclerListener.onMovedToScrapHeap(scrap);
  116. }
  117. }
  118.  
  119. /**
  120. * @return A view from the ScrapViews collection. These are unordered.
  121. */
  122. View getScrapView(int position) {
  123. ArrayList<View> scrapViews;
  124. if (mViewTypeCount == 1) {
  125. scrapViews = mCurrentScrap;
  126. int size = scrapViews.size();
  127. if (size > 0) {
  128. return scrapViews.remove(size - 1);
  129. } else {
  130. return null;
  131. }
  132. } else {
  133. int whichScrap = mAdapter.getItemViewType(position);
  134. if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
  135. scrapViews = mScrapViews[whichScrap];
  136. int size = scrapViews.size();
  137. if (size > 0) {
  138. return scrapViews.remove(size - 1);
  139. }
  140. }
  141. }
  142. return null;
  143. }
  144.  
  145. public void setViewTypeCount(int viewTypeCount) {
  146. if (viewTypeCount < 1) {
  147. throw new IllegalArgumentException("Can't have a viewTypeCount < 1");
  148. }
  149. // noinspection unchecked
  150. ArrayList<View>[] scrapViews = new ArrayList[viewTypeCount];
  151. for (int i = 0; i < viewTypeCount; i++) {
  152. scrapViews[i] = new ArrayList<View>();
  153. }
  154. mViewTypeCount = viewTypeCount;
  155. mCurrentScrap = scrapViews[0];
  156. mScrapViews = scrapViews;
  157. }
  158.  
  159. }

这里的RecycleBin代码并不全,我仅仅是把最基本的几个方法提了出来。

那么我们先来对这几个方法进行简单解读,这对后面分析ListView的工作原理将会有很大的帮助。

  • fillActiveViews() 这种方法接收两个參数,第一个參数表示要存储的view的数量,第二个參数表示ListView中第一个可见元素的position值。

    RecycleBin其中使用mActiveViews这个数组来存储View,调用这种方法后就会依据传入的參数来将ListView中的指定元素存储到mActiveViews数组其中。

  • getActiveView() 这种方法和fillActiveViews()是相应的,用于从mActiveViews数组其中获取数据。该方法接收一个position參数。表示元素在ListView其中的位置,方法内部会自己主动将position值转换成mActiveViews数组相应的下标值。

    须要注意的是,mActiveViews其中所存储的View,一旦被获取了之后就会从mActiveViews其中移除,下次获取相同位置的View将会返回null,也就是说mActiveViews不能被反复利用。

  • addScrapView() 用于将一个废弃的View进行缓存,该方法接收一个View參数,当有某个View确定要废弃掉的时候(比方滚动出了屏幕),就应该调用这种方法来对View进行缓存,RecycleBin其中使用mScrapViews和mCurrentScrap这两个List来存储废弃View。
  • getScrapView 用于从废弃缓存中取出一个View,这些废弃缓存中的View是没有顺序可言的。因此getScrapView()方法中的算法也很easy,就是直接从mCurrentScrap其中获取尾部的一个scrap view进行返回。

  • setViewTypeCount() 我们都知道Adapter其中能够重写一个getViewTypeCount()来表示ListView中有几种类型的数据项,而setViewTypeCount()方法的作用就是为每种类型的数据项都单独启用一个RecycleBin缓存机制。实际上,getViewTypeCount()方法通常情况下使用的并非许多。所以我们仅仅要知道RecycleBin其中有这样一个功能即可了。

了解了RecycleBin中的主要方法以及它们的用处之后。以下就能够開始来分析ListView的工作原理了。这里我将还是依照曾经分析源代码的方式来进行,即跟着主线运行流程来逐步阅读并点到即止,不然的话要是把ListView全部的代码都贴出来,那么本篇文章将会很长很长了。

第一次Layout

无论怎么说,ListView即使再特殊终于还是继承自View的。因此它的运行流程还将会依照View的规则来运行,对于这方面不太熟悉的朋友能够參考我之前写的 Android视图绘制流程全然解析。带你一步步深入了解View(二) 。

View的运行流程无非就分为三步,onMeasure()用于測量View的大小,onLayout()用于确定View的布局,onDraw()用于将View绘制到界面上。而在ListView其中,onMeasure()并没有什么特殊的地方。由于它终归是一个View,占用的空间最多而且通常也就是整个屏幕。onDraw()在ListView其中也没有什么意义。由于ListView本身并不负责绘制,而是由ListView其中的子元素来进行绘制的。那么ListView大部分的奇妙功能事实上都是在onLayout()方法中进行的了。因此我们本篇文章也是主要分析的这种方法里的内容。

假设你到ListView源代码中去找一找。你会发现ListView中是没有onLayout()这种方法的,这是由于这种方法是在ListView的父类AbsListView中实现的,代码例如以下所看到的:

  1. /**
  2. * Subclasses should NOT override this method but {@link #layoutChildren()}
  3. * 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();
  13. }
  14. mRecycler.markChildrenDirty();
  15. }
  16. layoutChildren();
  17. mInLayout = false;
  18. }

能够看到,onLayout()方法中并没有做什么复杂的逻辑操作,主要就是一个推断,假设ListView的大小或者位置发生了变化,那么changed变量就会变成true。此时会要求全部的子布局都强制进行重绘。除此之外倒没有什么难理解的地方了,只是我们注意到。在第16行调用了layoutChildren()这种方法,从方法名上我们就能够猜出这种方法是用来进行子元素布局的。只是进入到这种方法其中你会发现这是个空方法。没有一行代码。这当然是能够理解的了,由于子元素的布局应该是由详细的实现类来负责完毕的。而不是由父类完毕。那么进入ListView的layoutChildren()方法。代码例如以下所看到的:

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

这段代码比較长。我们挑重点的看。

首先能够确定的是。ListView其中眼下还没有不论什么子View,数据都还是由Adapter管理的,并没有展示到界面上,因此第19行getChildCount()方法得到的值肯定是0。接着在第81行会依据dataChanged这个布尔型的值来推断运行逻辑。dataChanged仅仅有在数据源发生改变的情况下才会变成true,其它情况都是false,因此这里会进入到第90行的运行逻辑。调用RecycleBin的fillActiveViews()方法。按理来说,调用fillActiveViews()方法是为了将ListView的子View进行缓存的。可是眼下ListView中还没有不论什么的子View。因此这一行临时还起不了不论什么作用。

接下来在第114行会依据mLayoutMode的值来决定布局模式,默认情况下都是普通模式LAYOUT_NORMAL,因此会进入到第140行的default语句其中。而以下又会紧接着进行两次if推断,childCount眼下是等于0的,而且默认的布局顺序是从上往下,因此会进入到第145行的fillFromTop()方法,我们跟进去瞧一瞧:

  1. /**
  2. * Fills the list from top to bottom, starting with mFirstPosition
  3. *
  4. * @param nextTop The location where the top of the first item should be
  5. * drawn
  6. *
  7. * @return The view that is currently selected
  8. */
  9. private View fillFromTop(int nextTop) {
  10. mFirstPosition = Math.min(mFirstPosition, mSelectedPosition);
  11. mFirstPosition = Math.min(mFirstPosition, mItemCount - 1);
  12. if (mFirstPosition < 0) {
  13. mFirstPosition = 0;
  14. }
  15. return fillDown(mFirstPosition, nextTop);
  16. }

从这种方法的凝视中能够看出。它所负责的主要任务就是从mFirstPosition開始,自顶至底去填充ListView。而这种方法本身并没有什么逻辑。就是推断了一下mFirstPosition值的合法性,然后调用fillDown()方法。那么我们就有理由能够推測,填充ListView的操作是在fillDown()方法中完毕的。进入fillDown()方法。代码例如以下所看到的:

  1. /**
  2. * Fills the list from pos down to the end of the list view.
  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. int end = (getBottom() - getTop()) - mListPadding.bottom;
  15. while (nextTop < end && pos < mItemCount) {
  16. // is this the selected item?
  17. boolean selected = pos == mSelectedPosition;
  18. View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected);
  19. nextTop = child.getBottom() + mDividerHeight;
  20. if (selected) {
  21. selectedView = child;
  22. }
  23. pos++;
  24. }
  25. return selectedView;
  26. }

能够看到,这里使用了一个while循环来运行反复逻辑,一開始nextTop的值是第一个子元素顶部距离整个ListView顶部的像素值,pos则是刚刚传入的mFirstPosition的值。而end是ListView底部减去顶部所得的像素值,mItemCount则是Adapter中的元素数量。因此一開始的情况下nextTop必然是小于end值的,而且pos也是小于mItemCount值的。那么每运行一次while循环,pos的值都会加1,而且nextTop也会添加。当nextTop大于等于end时,也就是子元素已经超出当前屏幕了,或者pos大于等于mItemCount时。也就是全部Adapter中的元素都被遍历结束了,就会跳出while循环。

那么while循环其中又做了什么事情呢?值得让人留意的就是第18行调用的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.
  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.  
  13. * @return View that was added
  14. */
  15. private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
  16. boolean selected) {
  17. View child;
  18. if (!mDataChanged) {
  19. // Try to use an exsiting view for this position
  20. child = mRecycler.getActiveView(position);
  21. if (child != null) {
  22. // Found it -- we're using an existing child
  23. // This just needs to be positioned
  24. setupChild(child, position, y, flow, childrenLeft, selected, true);
  25. return child;
  26. }
  27. }
  28. // Make a new view for this position, or convert an unused view if possible
  29. child = obtainView(position, mIsScrap);
  30. // This needs to be positioned and measured
  31. setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
  32. return child;
  33. }

这里在第19行尝试从RecycleBin其中高速获取一个active view,只是很遗憾的是眼下RecycleBin其中还没有缓存不论什么的View,所以这里得到的值肯定是null。

那么取得了null之后就会继续向下运行,到第28行会调用obtainView()方法来再次尝试获取一个View。这次的obtainView()方法是能够保证一定返回一个View的,于是以下立马将获取到的View传入到了setupChild()方法其中。那么obtainView()内部究竟是怎么工作的呢?我们先进入到这种方法里面看一下:

  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
  8. * The position to display
  9. * @param isScrap
  10. * Array of at least 1 boolean, the first entry will become true
  11. * if the returned view was taken from the scrap heap, false if
  12. * otherwise.
  13. *
  14. * @return A view displaying the data associated with the specified position
  15. */
  16. View obtainView(int position, boolean[] isScrap) {
  17. isScrap[0] = false;
  18. View scrapView;
  19. scrapView = mRecycler.getScrapView(position);
  20. View child;
  21. if (scrapView != null) {
  22. child = mAdapter.getView(position, scrapView, this);
  23. if (child != scrapView) {
  24. mRecycler.addScrapView(scrapView);
  25. if (mCacheColorHint != 0) {
  26. child.setDrawingCacheBackgroundColor(mCacheColorHint);
  27. }
  28. } else {
  29. isScrap[0] = true;
  30. dispatchFinishTemporaryDetach(child);
  31. }
  32. } else {
  33. child = mAdapter.getView(position, null, this);
  34. if (mCacheColorHint != 0) {
  35. child.setDrawingCacheBackgroundColor(mCacheColorHint);
  36. }
  37. }
  38. return child;
  39. }

obtainView()方法中的代码并不多。但却包括了很很重要的逻辑。不夸张的说。整个ListView中最重要的内容可能就在这种方法里了。那么我们还是依照运行流程来看,在第19行代码中调用了RecycleBin的getScrapView()方法来尝试获取一个废弃缓存中的View,相同的道理,这里肯定是获取不到的,getScrapView()方法会返回一个null。这时该怎么办呢?没有关系,代码会运行到第33行,调用mAdapter的getView()方法来去获取一个View。那么mAdapter是什么呢?当然就是当前ListView关联的适配器了。而getView()方法又是什么呢?还用说吗,这个就是我们平时使用ListView时最最经常重写的一个方法了,这里getView()方法中传入了三个參数,各自是position,null和this。

那么我们平时写ListView的Adapter时,getView()方法一般会怎么写呢?这里我举个简单的样例:

  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {
  3. Fruit fruit = getItem(position);
  4. View view;
  5. if (convertView == null) {
  6. view = LayoutInflater.from(getContext()).inflate(resourceId, null);
  7. } else {
  8. view = convertView;
  9. }
  10. ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
  11. TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
  12. fruitImage.setImageResource(fruit.getImageId());
  13. fruitName.setText(fruit.getName());
  14. return view;
  15. }

getView()方法接受的三个參数,第一个參数position代表当前子元素的的位置。我们能够通过详细的位置来获取与其相关的数据。第二个參数convertView,刚才传入的是null。说明没有convertView能够利用,因此我们会调用LayoutInflater的inflate()方法来去载入一个布局。

接下来会对这个view进行一些属性和值的设定。最后将view返回。

那么这个View也会作为obtainView()的结果进行返回,并终于传入到setupChild()方法其中。事实上也就是说,第一次layout过程其中。全部的子View都是调用LayoutInflater的inflate()方法载入出来的,这样就会相对照较耗时。可是不用操心。后面就不会再有这样的情况了,那么我们继续往下看:

  1. /**
  2. * Add a view as a child and make sure it is measured (if necessary) and
  3. * positioned properly.
  4. *
  5. * @param child The view to add
  6. * @param position The position of this child
  7. * @param y The y position relative to which this view will be positioned
  8. * @param flowDown If 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.  
  13. * @param recycled Has this view been pulled from the recycle bin?
  14.  
  15. If so it
  16. * does not need to be remeasured.
  17. */
  18. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
  19. boolean selected, boolean recycled) {
  20. final boolean isSelected = selected && shouldShowSelector();
  21. final boolean updateChildSelected = isSelected != child.isSelected();
  22. final int mode = mTouchMode;
  23. final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
  24. mMotionPosition == position;
  25. final boolean updateChildPressed = isPressed != child.isPressed();
  26. final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
  27. // Respect layout params that are already in the view. Otherwise make some up...
  28. // noinspection unchecked
  29. AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
  30. if (p == null) {
  31. p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  32. ViewGroup.LayoutParams.WRAP_CONTENT, 0);
  33. }
  34. p.viewType = mAdapter.getItemViewType(position);
  35. if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
  36. p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
  37. attachViewToParent(child, flowDown ? -1 : 0, p);
  38. } else {
  39. p.forceAdd = false;
  40. if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
  41. p.recycledHeaderFooter = true;
  42. }
  43. addViewInLayout(child, flowDown ?
  44.  
  45. -1 : 0, p, true);
  46. }
  47. if (updateChildSelected) {
  48. child.setSelected(isSelected);
  49. }
  50. if (updateChildPressed) {
  51. child.setPressed(isPressed);
  52. }
  53. if (needToMeasure) {
  54. int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
  55. mListPadding.left + mListPadding.right, p.width);
  56. int lpHeight = p.height;
  57. int childHeightSpec;
  58. if (lpHeight > 0) {
  59. childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
  60. } else {
  61. childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  62. }
  63. child.measure(childWidthSpec, childHeightSpec);
  64. } else {
  65. cleanupLayoutState(child);
  66. }
  67. final int w = child.getMeasuredWidth();
  68. final int h = child.getMeasuredHeight();
  69. final int childTop = flowDown ? y : y - h;
  70. if (needToMeasure) {
  71. final int childRight = childrenLeft + w;
  72. final int childBottom = childTop + h;
  73. child.layout(childrenLeft, childTop, childRight, childBottom);
  74. } else {
  75. child.offsetLeftAndRight(childrenLeft - child.getLeft());
  76. child.offsetTopAndBottom(childTop - child.getTop());
  77. }
  78. if (mCachingStarted && !child.isDrawingCacheEnabled()) {
  79. child.setDrawingCacheEnabled(true);
  80. }
  81. }

setupChild()方法其中的代码尽管比較多,可是我们仅仅看核心代码的话就很easy了。刚才调用obtainView()方法获取到的子元素View,这里在第40行调用了addViewInLayout()方法将它加入到了ListView其中。那么依据fillDown()方法中的while循环,会让子元素View将整个ListView控件填满然后就跳出。也就是说即使我们的Adapter中有一千条数据,ListView也仅仅会载入第一屏的数据,剩下的数据反正眼下在屏幕上也看不到,所以不会去做多余的载入工作。这样就能够保证ListView中的内容能够迅速展示到屏幕上。

那么到此为止,第一次Layout过程结束。

第二次Layout

尽管我在源代码中并没有找出详细的原因。但假设你自己做一下实验的话就会发现,即使是一个再简单的View,在展示到界面上之前都会经历至少两次onMeasure()和两次onLayout()的过程。事实上这仅仅是一个很小的细节。平时对我们影响并不大。由于无论是onMeasure()或者onLayout()几次。反正都是运行的相同的逻辑,我们并不须要进行过多关心。可是在ListView中情况就不一样了。由于这就意味着layoutChildren()过程会运行两次,而这个过程其中涉及到向ListView中加入子元素,假设相同的逻辑运行两遍的话,那么ListView中就会存在一份反复的数据了。因此ListView在layoutChildren()过程其中做了第二次Layout的逻辑处理。很巧妙地攻克了这个问题。以下我们就来分析一下第二次Layout的过程。

事实上第二次Layout和第一次Layout的基本流程是差点儿相同的,那么我们还是从layoutChildren()方法開始看起:

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

相同还是在第19行,调用getChildCount()方法来获取子View的数量。仅仅只是如今得到的值不会再是0了。而是ListView中一屏能够显示的子View数量,由于我们刚刚在第一次Layout过程其中向ListView加入了这么多的子View。以下在第90行调用了RecycleBin的fillActiveViews()方法,这次效果可就不一样了,由于眼下ListView中已经有子View了,这样全部的子View都会被缓存到RecycleBin的mActiveViews数组其中,后面将会用到它们。

接下来将会是很很重要的一个操作,在第113行调用了detachAllViewsFromParent()方法。这种方法会将全部ListView其中的子View全部清除掉,从而保证第二次Layout过程不会产生一份反复的数据。那有的朋友可能会问了。这样把已经载入好的View又清除掉,待会还要再又一次载入一遍,这不是严重影响效率吗?不用操心。还记得我们刚刚调用了RecycleBin的fillActiveViews()方法来缓存子View吗。待会儿将会直接使用这些缓存好的View来进行载入,而并不会又一次运行一遍inflate过程。因此效率方面并不会有什么明显的影响。

那么我们接着看。在第141行的推断逻辑其中,由于不再等于0了。因此会进入到else语句其中。

而else语句中又有三个逻辑推断。第一个逻辑推断不成立,由于默认情况下我们没有选中不论什么子元素,mSelectedPosition应该等于-1。第二个逻辑推断一般是成立的。由于mFirstPosition的值一開始是等于0的。仅仅要adapter中的数据大于0条件就成立。那么进入到fillSpecific()方法其中,代码例如以下所看到的:

  1. /**
  2. * Put a specific item at a specific location on the screen and then build
  3. * up and down from there.
  4. *
  5. * @param position The reference view to use as the starting point
  6. * @param top Pixel offset from the top of this view to the top of the
  7. * reference view.
  8. *
  9. * @return The selected view, or null if the selected view is outside the
  10. * visible area.
  11. */
  12. private View fillSpecific(int position, int top) {
  13. boolean tempIsSelected = position == mSelectedPosition;
  14. View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected);
  15. // Possibly changed again in fillUp if we add rows above this one.
  16. mFirstPosition = position;
  17. View above;
  18. View below;
  19. final int dividerHeight = mDividerHeight;
  20. if (!mStackFromBottom) {
  21. above = fillUp(position - 1, temp.getTop() - dividerHeight);
  22. // This will correct for the top of the first view not touching the top of the list
  23. adjustViewsUpOrDown();
  24. below = fillDown(position + 1, temp.getBottom() + dividerHeight);
  25. int childCount = getChildCount();
  26. if (childCount > 0) {
  27. correctTooHigh(childCount);
  28. }
  29. } else {
  30. below = fillDown(position + 1, temp.getBottom() + dividerHeight);
  31. // This will correct for the bottom of the last view not touching the bottom of the list
  32. adjustViewsUpOrDown();
  33. above = fillUp(position - 1, temp.getTop() - dividerHeight);
  34. int childCount = getChildCount();
  35. if (childCount > 0) {
  36. correctTooLow(childCount);
  37. }
  38. }
  39. if (tempIsSelected) {
  40. return temp;
  41. } else if (above != null) {
  42. return above;
  43. } else {
  44. return below;
  45. }
  46. }

fillSpecific()这算是一个新方法了。只是事实上它和fillUp()、fillDown()方法功能也是差点儿相同的。基本的差别在于,fillSpecific()方法会优先将指定位置的子View先载入到屏幕上,然后再载入该子View往上以及往下的其它子View。

那么由于这里我们传入的position就是第一个子View的位置。于是fillSpecific()方法的作用就基本上和fillDown()方法是差点儿相同的了,这里我们就不去关注太多它的细节。而是将精力放在makeAndAddView()方法上面。再次回到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.
  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. if (!mDataChanged) {
  18. // Try to use an exsiting view for this position
  19. child = mRecycler.getActiveView(position);
  20. if (child != null) {
  21. // Found it -- we're using an existing child
  22. // This just needs to be positioned
  23. setupChild(child, position, y, flow, childrenLeft, selected, true);
  24. return child;
  25. }
  26. }
  27. // Make a new view for this position, or convert an unused view if possible
  28. child = obtainView(position, mIsScrap);
  29. // This needs to be positioned and measured
  30. setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
  31. return child;
  32. }

仍然还是在第19行尝试从RecycleBin其中获取Active View,然而这次就一定能够获取到了,由于前面我们调用了RecycleBin的fillActiveViews()方法来缓存子View。那么既然如此,就不会再进入到第28行的obtainView()方法,而是会直接进入setupChild()方法其中。这样也省去了许多时间,由于假设在obtainView()方法中又要去infalte布局的话,那么ListView的初始载入效率就大大减少了。

注意在第23行。setupChild()方法的最后一个參数传入的是true,这个參数表明当前的View是之前被回收过的,那么我们再次回到setupChild()方法其中:

  1. /**
  2. * Add a view as a child and make sure it is measured (if necessary) and
  3. * positioned properly.
  4. *
  5. * @param child The view to add
  6. * @param position The position of this child
  7. * @param y The y position relative to which this view will be positioned
  8. * @param flowDown If 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. * @param recycled Has this view been pulled from the recycle bin? If so it
  13. * does not need to be remeasured.
  14. */
  15. private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft,
  16. boolean selected, boolean recycled) {
  17. final boolean isSelected = selected && shouldShowSelector();
  18. final boolean updateChildSelected = isSelected != child.isSelected();
  19. final int mode = mTouchMode;
  20. final boolean isPressed = mode > TOUCH_MODE_DOWN && mode < TOUCH_MODE_SCROLL &&
  21. mMotionPosition == position;
  22. final boolean updateChildPressed = isPressed != child.isPressed();
  23. final boolean needToMeasure = !recycled || updateChildSelected || child.isLayoutRequested();
  24. // Respect layout params that are already in the view. Otherwise make some up...
  25. // noinspection unchecked
  26. AbsListView.LayoutParams p = (AbsListView.LayoutParams) child.getLayoutParams();
  27. if (p == null) {
  28. p = new AbsListView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
  29. ViewGroup.LayoutParams.WRAP_CONTENT, 0);
  30. }
  31. p.viewType = mAdapter.getItemViewType(position);
  32. if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter &&
  33. p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) {
  34. attachViewToParent(child, flowDown ? -1 : 0, p);
  35. } else {
  36. p.forceAdd = false;
  37. if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
  38. p.recycledHeaderFooter = true;
  39. }
  40. addViewInLayout(child, flowDown ?
  41.  
  42. -1 : 0, p, true);
  43. }
  44. if (updateChildSelected) {
  45. child.setSelected(isSelected);
  46. }
  47. if (updateChildPressed) {
  48. child.setPressed(isPressed);
  49. }
  50. if (needToMeasure) {
  51. int childWidthSpec = ViewGroup.getChildMeasureSpec(mWidthMeasureSpec,
  52. mListPadding.left + mListPadding.right, p.width);
  53. int lpHeight = p.height;
  54. int childHeightSpec;
  55. if (lpHeight > 0) {
  56. childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
  57. } else {
  58. childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
  59. }
  60. child.measure(childWidthSpec, childHeightSpec);
  61. } else {
  62. cleanupLayoutState(child);
  63. }
  64. final int w = child.getMeasuredWidth();
  65. final int h = child.getMeasuredHeight();
  66. final int childTop = flowDown ? y : y - h;
  67. if (needToMeasure) {
  68. final int childRight = childrenLeft + w;
  69. final int childBottom = childTop + h;
  70. child.layout(childrenLeft, childTop, childRight, childBottom);
  71. } else {
  72. child.offsetLeftAndRight(childrenLeft - child.getLeft());
  73. child.offsetTopAndBottom(childTop - child.getTop());
  74. }
  75. if (mCachingStarted && !child.isDrawingCacheEnabled()) {
  76. child.setDrawingCacheEnabled(true);
  77. }
  78. }

能够看到,setupChild()方法的最后一个參数是recycled。然后在第32行会对这个变量进行推断,由于recycled如今是true。所以会运行attachViewToParent()方法。而第一次Layout过程则是运行的else语句中的addViewInLayout()方法。这两个方法最大的差别在于。假设我们须要向ViewGroup中加入一个新的子View,应该调用addViewInLayout()方法。而假设是想要将一个之前detach的View又一次attach到ViewGroup上,就应该调用attachViewToParent()方法。那么由于前面在layoutChildren()方法其中调用了detachAllViewsFromParent()方法,这样ListView中全部的子View都是处于detach状态的,所以这里attachViewToParent()方法是正确的选择。

经历了这样一个detach又attach的过程,ListView中全部的子View又都能够正常显示出来了,那么第二次Layout过程结束。

滑动载入许多其它数据

经历了两次Layout过程,虽说我们已经能够在ListView中看到内容了,然而关于ListView最奇妙的部分我们却还没有接触到,由于眼下ListView中仅仅是载入并显示了第一屏的数据而已。比方说我们的Adapter其中有1000条数据,可是第一屏仅仅显示了10条,ListView中也仅仅有10个子View而已,那么剩下的990是如何工作并显示到界面上的呢?这就要看一下ListView滑动部分的源代码了,由于我们是通过手指滑动来显示许多其它数据的。

由于滑动部分的机制是属于通用型的,即ListView和GridView都会使用相同的机制。因此这部分代码就肯定是写在AbsListView其中的了。那么监听触控事件是在onTouchEvent()方法其中进行的。我们就来看一下AbsListView中的这种方法:

  1. @Override
  2. public boolean onTouchEvent(MotionEvent ev) {
  3. if (!isEnabled()) {
  4. // A disabled view that is clickable still consumes the touch
  5. // events, it just doesn't respond to them.
  6. return isClickable() || isLongClickable();
  7. }
  8. final int action = ev.getAction();
  9. View v;
  10. int deltaY;
  11. if (mVelocityTracker == null) {
  12. mVelocityTracker = VelocityTracker.obtain();
  13. }
  14. mVelocityTracker.addMovement(ev);
  15. switch (action & MotionEvent.ACTION_MASK) {
  16. case MotionEvent.ACTION_DOWN: {
  17. mActivePointerId = ev.getPointerId(0);
  18. final int x = (int) ev.getX();
  19. final int y = (int) ev.getY();
  20. int motionPosition = pointToPosition(x, y);
  21. if (!mDataChanged) {
  22. if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
  23. && (getAdapter().isEnabled(motionPosition))) {
  24. // User clicked on an actual view (and was not stopping a
  25. // fling). It might be a
  26. // click or a scroll. Assume it is a click until proven
  27. // otherwise
  28. mTouchMode = TOUCH_MODE_DOWN;
  29. // FIXME Debounce
  30. if (mPendingCheckForTap == null) {
  31. mPendingCheckForTap = new CheckForTap();
  32. }
  33. postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
  34. } else {
  35. if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
  36. // If we couldn't find a view to click on, but the down
  37. // event was touching
  38. // the edge, we will bail out and try again. This allows
  39. // the edge correcting
  40. // code in ViewRoot to try to find a nearby view to
  41. // select
  42. return false;
  43. }
  44.  
  45. if (mTouchMode == TOUCH_MODE_FLING) {
  46. // Stopped a fling. It is a scroll.
  47. createScrollingCache();
  48. mTouchMode = TOUCH_MODE_SCROLL;
  49. mMotionCorrection = 0;
  50. motionPosition = findMotionRow(y);
  51. reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
  52. }
  53. }
  54. }
  55. if (motionPosition >= 0) {
  56. // Remember where the motion event started
  57. v = getChildAt(motionPosition - mFirstPosition);
  58. mMotionViewOriginalTop = v.getTop();
  59. }
  60. mMotionX = x;
  61. mMotionY = y;
  62. mMotionPosition = motionPosition;
  63. mLastY = Integer.MIN_VALUE;
  64. break;
  65. }
  66. case MotionEvent.ACTION_MOVE: {
  67. final int pointerIndex = ev.findPointerIndex(mActivePointerId);
  68. final int y = (int) ev.getY(pointerIndex);
  69. deltaY = y - mMotionY;
  70. switch (mTouchMode) {
  71. case TOUCH_MODE_DOWN:
  72. case TOUCH_MODE_TAP:
  73. case TOUCH_MODE_DONE_WAITING:
  74. // Check if we have moved far enough that it looks more like a
  75. // scroll than a tap
  76. startScrollIfNeeded(deltaY);
  77. break;
  78. case TOUCH_MODE_SCROLL:
  79. if (PROFILE_SCROLLING) {
  80. if (!mScrollProfilingStarted) {
  81. Debug.startMethodTracing("AbsListViewScroll");
  82. mScrollProfilingStarted = true;
  83. }
  84. }
  85. if (y != mLastY) {
  86. deltaY -= mMotionCorrection;
  87. int incrementalDeltaY = mLastY != Integer.MIN_VALUE ?
  88.  
  89. y - mLastY : deltaY;
  90. // No need to do all this work if we're not going to move
  91. // anyway
  92. boolean atEdge = false;
  93. if (incrementalDeltaY != 0) {
  94. atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
  95. }
  96. // Check to see if we have bumped into the scroll limit
  97. if (atEdge && getChildCount() > 0) {
  98. // Treat this like we're starting a new scroll from the
  99. // current
  100. // position. This will let the user start scrolling back
  101. // into
  102. // content immediately rather than needing to scroll
  103. // back to the
  104. // point where they hit the limit first.
  105. int motionPosition = findMotionRow(y);
  106. if (motionPosition >= 0) {
  107. final View motionView = getChildAt(motionPosition - mFirstPosition);
  108. mMotionViewOriginalTop = motionView.getTop();
  109. }
  110. mMotionY = y;
  111. mMotionPosition = motionPosition;
  112. invalidate();
  113. }
  114. mLastY = y;
  115. }
  116. break;
  117. }
  118. break;
  119. }
  120. case MotionEvent.ACTION_UP: {
  121. switch (mTouchMode) {
  122. case TOUCH_MODE_DOWN:
  123. case TOUCH_MODE_TAP:
  124. case TOUCH_MODE_DONE_WAITING:
  125. final int motionPosition = mMotionPosition;
  126. final View child = getChildAt(motionPosition - mFirstPosition);
  127. if (child != null && !child.hasFocusable()) {
  128. if (mTouchMode != TOUCH_MODE_DOWN) {
  129. child.setPressed(false);
  130. }
  131. if (mPerformClick == null) {
  132. mPerformClick = new PerformClick();
  133. }
  134. final AbsListView.PerformClick performClick = mPerformClick;
  135. performClick.mChild = child;
  136. performClick.mClickMotionPosition = motionPosition;
  137. performClick.rememberWindowAttachCount();
  138. mResurrectToPosition = motionPosition;
  139. if (mTouchMode == TOUCH_MODE_DOWN || mTouchMode == TOUCH_MODE_TAP) {
  140. final Handler handler = getHandler();
  141. if (handler != null) {
  142. handler.removeCallbacks(mTouchMode == TOUCH_MODE_DOWN ? mPendingCheckForTap
  143. : mPendingCheckForLongPress);
  144. }
  145. mLayoutMode = LAYOUT_NORMAL;
  146. if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  147. mTouchMode = TOUCH_MODE_TAP;
  148. setSelectedPositionInt(mMotionPosition);
  149. layoutChildren();
  150. child.setPressed(true);
  151. positionSelector(child);
  152. setPressed(true);
  153. if (mSelector != null) {
  154. Drawable d = mSelector.getCurrent();
  155. if (d != null && d instanceof TransitionDrawable) {
  156. ((TransitionDrawable) d).resetTransition();
  157. }
  158. }
  159. postDelayed(new Runnable() {
  160. public void run() {
  161. child.setPressed(false);
  162. setPressed(false);
  163. if (!mDataChanged) {
  164. post(performClick);
  165. }
  166. mTouchMode = TOUCH_MODE_REST;
  167. }
  168. }, ViewConfiguration.getPressedStateDuration());
  169. } else {
  170. mTouchMode = TOUCH_MODE_REST;
  171. }
  172. return true;
  173. } else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
  174. post(performClick);
  175. }
  176. }
  177. mTouchMode = TOUCH_MODE_REST;
  178. break;
  179. case TOUCH_MODE_SCROLL:
  180. final int childCount = getChildCount();
  181. if (childCount > 0) {
  182. if (mFirstPosition == 0
  183. && getChildAt(0).getTop() >= mListPadding.top
  184. && mFirstPosition + childCount < mItemCount
  185. && getChildAt(childCount - 1).getBottom() <= getHeight()
  186. - mListPadding.bottom) {
  187. mTouchMode = TOUCH_MODE_REST;
  188. reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  189. } else {
  190. final VelocityTracker velocityTracker = mVelocityTracker;
  191. velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
  192. final int initialVelocity = (int) velocityTracker
  193. .getYVelocity(mActivePointerId);
  194. if (Math.abs(initialVelocity) > mMinimumVelocity) {
  195. if (mFlingRunnable == null) {
  196. mFlingRunnable = new FlingRunnable();
  197. }
  198. reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
  199. mFlingRunnable.start(-initialVelocity);
  200. } else {
  201. mTouchMode = TOUCH_MODE_REST;
  202. reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  203. }
  204. }
  205. } else {
  206. mTouchMode = TOUCH_MODE_REST;
  207. reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
  208. }
  209. break;
  210. }
  211. setPressed(false);
  212. // Need to redraw since we probably aren't drawing the selector
  213. // anymore
  214. invalidate();
  215. final Handler handler = getHandler();
  216. if (handler != null) {
  217. handler.removeCallbacks(mPendingCheckForLongPress);
  218. }
  219. if (mVelocityTracker != null) {
  220. mVelocityTracker.recycle();
  221. mVelocityTracker = null;
  222. }
  223. mActivePointerId = INVALID_POINTER;
  224. if (PROFILE_SCROLLING) {
  225. if (mScrollProfilingStarted) {
  226. Debug.stopMethodTracing();
  227. mScrollProfilingStarted = false;
  228. }
  229. }
  230. break;
  231. }
  232. case MotionEvent.ACTION_CANCEL: {
  233. mTouchMode = TOUCH_MODE_REST;
  234. setPressed(false);
  235. View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
  236. if (motionView != null) {
  237. motionView.setPressed(false);
  238. }
  239. clearScrollingCache();
  240. final Handler handler = getHandler();
  241. if (handler != null) {
  242. handler.removeCallbacks(mPendingCheckForLongPress);
  243. }
  244. if (mVelocityTracker != null) {
  245. mVelocityTracker.recycle();
  246. mVelocityTracker = null;
  247. }
  248. mActivePointerId = INVALID_POINTER;
  249. break;
  250. }
  251. case MotionEvent.ACTION_POINTER_UP: {
  252. onSecondaryPointerUp(ev);
  253. final int x = mMotionX;
  254. final int y = mMotionY;
  255. final int motionPosition = pointToPosition(x, y);
  256. if (motionPosition >= 0) {
  257. // Remember where the motion event started
  258. v = getChildAt(motionPosition - mFirstPosition);
  259. mMotionViewOriginalTop = v.getTop();
  260. mMotionPosition = motionPosition;
  261. }
  262. mLastY = y;
  263. break;
  264. }
  265. }
  266. return true;
  267. }

这种方法中的代码就许多了,由于它所处理的逻辑或许多,要监听各种各样的触屏事件。

可是我们眼下所关心的就仅仅有手指在屏幕上滑动这一个事件而已。相应的是ACTION_MOVE这个动作,那么我们就仅仅看这部分代码就能够了。

能够看到。ACTION_MOVE这个case里面又嵌套了一个switch语句,是依据当前的TouchMode来选择的。那这里我能够直接告诉大家,当手指在屏幕上滑动时,TouchMode是等于TOUCH_MODE_SCROLL这个值的,至于为什么那又要牵扯到另外的好几个方法。这里限于篇幅原因就不再展开解说了,喜欢寻根究底的朋友们能够自己去源代码里找一找原因。

这样的话。代码就应该会走到第78行的这个case里面去了,在这个case其中并没有什么太多须要注意的东西。唯一一点很重要的就是第92行调用的trackMotionScroll()方法。相当于我们手指仅仅要在屏幕上略微有一点点移动。这种方法就会被调用,而假设是正常在屏幕上滑动的话,那么这种方法就会被调用许多次。

那么我们进入到这种方法中瞧一瞧,代码例如以下所看到的:

  1. boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
  2. final int childCount = getChildCount();
  3. if (childCount == 0) {
  4. return true;
  5. }
  6. final int firstTop = getChildAt(0).getTop();
  7. final int lastBottom = getChildAt(childCount - 1).getBottom();
  8. final Rect listPadding = mListPadding;
  9. final int spaceAbove = listPadding.top - firstTop;
  10. final int end = getHeight() - listPadding.bottom;
  11. final int spaceBelow = lastBottom - end;
  12. final int height = getHeight() - getPaddingBottom() - getPaddingTop();
  13. if (deltaY < 0) {
  14. deltaY = Math.max(-(height - 1), deltaY);
  15. } else {
  16. deltaY = Math.min(height - 1, deltaY);
  17. }
  18. if (incrementalDeltaY < 0) {
  19. incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
  20. } else {
  21. incrementalDeltaY = Math.min(height - 1, incrementalDeltaY);
  22. }
  23. final int firstPosition = mFirstPosition;
  24. if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
  25. // Don't need to move views down if the top of the first position
  26. // is already visible
  27. return true;
  28. }
  29. if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
  30. // Don't need to move views up if the bottom of the last position
  31. // is already visible
  32. return true;
  33. }
  34. final boolean down = incrementalDeltaY < 0;
  35. final boolean inTouchMode = isInTouchMode();
  36. if (inTouchMode) {
  37. hideSelector();
  38. }
  39. final int headerViewsCount = getHeaderViewsCount();
  40. final int footerViewsStart = mItemCount - getFooterViewsCount();
  41. int start = 0;
  42. int count = 0;
  43. if (down) {
  44. final int top = listPadding.top - incrementalDeltaY;
  45. for (int i = 0; i < childCount; i++) {
  46. final View child = getChildAt(i);
  47. if (child.getBottom() >= top) {
  48. break;
  49. } else {
  50. count++;
  51. int position = firstPosition + i;
  52. if (position >= headerViewsCount && position < footerViewsStart) {
  53. mRecycler.addScrapView(child);
  54. }
  55. }
  56. }
  57. } else {
  58. final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY;
  59. for (int i = childCount - 1; i >= 0; i--) {
  60. final View child = getChildAt(i);
  61. if (child.getTop() <= bottom) {
  62. break;
  63. } else {
  64. start = i;
  65. count++;
  66. int position = firstPosition + i;
  67. if (position >= headerViewsCount && position < footerViewsStart) {
  68. mRecycler.addScrapView(child);
  69. }
  70. }
  71. }
  72. }
  73. mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
  74. mBlockLayoutRequests = true;
  75. if (count > 0) {
  76. detachViewsFromParent(start, count);
  77. }
  78. offsetChildrenTopAndBottom(incrementalDeltaY);
  79. if (down) {
  80. mFirstPosition += count;
  81. }
  82. invalidate();
  83. final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
  84. if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
  85. fillGap(down);
  86. }
  87. if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
  88. final int childIndex = mSelectedPosition - mFirstPosition;
  89. if (childIndex >= 0 && childIndex < getChildCount()) {
  90. positionSelector(getChildAt(childIndex));
  91. }
  92. }
  93. mBlockLayoutRequests = false;
  94. invokeOnItemScrollListener();
  95. awakenScrollBars();
  96. return false;
  97. }

这种方法接收两个參数,deltaY表示从手指按下时的位置到当前手指位置的距离,incrementalDeltaY则表示据上次触发event事件手指在Y方向上位置的改变量。那么事实上我们就能够通过incrementalDeltaY的正负值情况来推断用户是向上还是向下滑动的了。如第34行代码所看到的,假设incrementalDeltaY小于0,说明是向下滑动。否则就是向上滑动。

以下将会进行一个边界值检測的过程。能够看到。从第43行開始,当ListView向下滑动的时候。就会进入一个for循环其中。从上往下依次获取子View,第47行其中,假设该子View的bottom值已经小于top值了。就说明这个子View已经移出屏幕了。所以会调用RecycleBin的addScrapView()方法将这个View加入到废弃缓存其中。并将count计数器加1,计数器用于记录有多少个子View被移出了屏幕。

那么假设是ListView向上滑动的话,事实上过程是基本相同的,仅仅只是变成了从下往上依次获取子View,然后推断该子View的top值是不是大于bottom值了,假设大于的话说明子View已经移出了屏幕,相同把它加入到废弃缓存中,并将计数器加1。

接下来在第76行。会依据当前计数器的值来进行一个detach操作,它的作用就是把全部移出屏幕的子View全部detach掉,在ListView的概念其中。全部看不到的View就没有必要为它进行保存,由于屏幕外还有成百上千条数据等着显示呢,一个好的回收策略才干保证ListView的高性能和高效率。

紧接着在第78行调用了offsetChildrenTopAndBottom()方法,并将incrementalDeltaY作为參数传入,这种方法的作用是让ListView中全部的子View都依照传入的參数值进行相应的偏移,这样就实现了随着手指的拖动,ListView的内容也会随着滚动的效果。

然后在第84行会进行推断,假设ListView中最后一个View的底部已经移入了屏幕,或者ListView中第一个View的顶部移入了屏幕,就会调用fillGap()方法,那么因此我们就能够猜出fillGap()方法是用来载入屏幕外数据的,进入到这种方法中瞧一瞧。例如以下所看到的:

  1. /**
  2. * Fills the gap left open by a touch-scroll. During a touch scroll,
  3. * children that remain on screen are shifted and the other ones are
  4. * discarded. The role of this method is to fill the gap thus created by
  5. * performing a partial layout in the empty space.
  6. *
  7. * @param down
  8. * true if the scroll is going down, false if it is going up
  9. */
  10. abstract void fillGap(boolean down);

OK,AbsListView中的fillGap()是一个抽象方法,那么我们立马就能够想到。它的详细实现肯定是在ListView中完毕的了。回到ListView其中。fillGap()方法的代码例如以下所看到的:

  1. void fillGap(boolean down) {
  2. final int count = getChildCount();
  3. if (down) {
  4. final int startOffset = count > 0 ?
  5.  
  6. getChildAt(count - 1).getBottom() + mDividerHeight :
  7. getListPaddingTop();
  8. fillDown(mFirstPosition + count, startOffset);
  9. correctTooHigh(getChildCount());
  10. } else {
  11. final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight :
  12. getHeight() - getListPaddingBottom();
  13. fillUp(mFirstPosition - 1, startOffset);
  14. correctTooLow(getChildCount());
  15. }
  16. }

down參数用于表示ListView是向下滑动还是向上滑动的,能够看到。假设是向下滑动的话就会调用fillDown()方法,而假设是向上滑动的话就会调用fillUp()方法。

那么这两个方法我们都已经很熟悉了,内部都是通过一个循环来去对ListView进行填充,所以这两个方法我们就不看了,可是填充ListView会通过调用makeAndAddView()方法来完毕,又是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.
  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. if (!mDataChanged) {
  18. // Try to use an exsiting view for this position
  19. child = mRecycler.getActiveView(position);
  20. if (child != null) {
  21. // Found it -- we're using an existing child
  22. // This just needs to be positioned
  23. setupChild(child, position, y, flow, childrenLeft, selected, true);
  24. return child;
  25. }
  26. }
  27. // Make a new view for this position, or convert an unused view if possible
  28. child = obtainView(position, mIsScrap);
  29. // This needs to be positioned and measured
  30. setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
  31. return child;
  32. }

无论怎么说。这里首先仍然是会尝试调用RecycleBin的getActiveView()方法来获取子布局,仅仅只是肯定是获取不到的了。由于在第二次Layout过程中我们已经从mActiveViews中获取过了数据,而依据RecycleBin的机制。mActiveViews是不能够反复利用的,因此这里返回的值肯定是null。

既然getActiveView()方法返回的值是null,那么就还是会走到第28行的obtainView()方法其中。代码例如以下所看到的:

  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
  8. * The position to display
  9. * @param isScrap
  10. * Array of at least 1 boolean, the first entry will become true
  11. * if the returned view was taken from the scrap heap, false if
  12. * otherwise.
  13. *
  14. * @return A view displaying the data associated with the specified position
  15. */
  16. View obtainView(int position, boolean[] isScrap) {
  17. isScrap[0] = false;
  18. View scrapView;
  19. scrapView = mRecycler.getScrapView(position);
  20. View child;
  21. if (scrapView != null) {
  22. child = mAdapter.getView(position, scrapView, this);
  23. if (child != scrapView) {
  24. mRecycler.addScrapView(scrapView);
  25. if (mCacheColorHint != 0) {
  26. child.setDrawingCacheBackgroundColor(mCacheColorHint);
  27. }
  28. } else {
  29. isScrap[0] = true;
  30. dispatchFinishTemporaryDetach(child);
  31. }
  32. } else {
  33. child = mAdapter.getView(position, null, this);
  34. if (mCacheColorHint != 0) {
  35. child.setDrawingCacheBackgroundColor(mCacheColorHint);
  36. }
  37. }
  38. return child;
  39. }

这里在第19行会调用RecyleBin的getScrapView()方法来尝试从废弃缓存中获取一个View,那么废弃缓存有没有View呢?当然有。由于刚才在trackMotionScroll()方法中我们就已经看到了,一旦有不论什么子View被移出了屏幕,就会将它加入到废弃缓存中,而从obtainView()方法中的逻辑来看。一旦有新的数据须要显示到屏幕上。就会尝试从废弃缓存中获取View。所以它们之间就形成了一个生产者和消费者的模式,那么ListView奇妙的地方也就在这里体现出来了。无论你有随意多条数据须要显示。ListView中的子View事实上来来回回就那么几个,移出屏幕的子View会很快被移入屏幕的数据又一次利用起来,因而无论我们载入多少数据都不会出现OOM的情况,甚至内存都不会有所添加。

那么另外另一点是须要大家留意的。这里获取到了一个scrapView,然后我们在第22行将它作为第二个參数传入到了Adapter的getView()方法其中。那么第二个參数是什么意思呢?我们再次看一下一个简单的getView()方法演示样例:

  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {
  3. Fruit fruit = getItem(position);
  4. View view;
  5. if (convertView == null) {
  6. view = LayoutInflater.from(getContext()).inflate(resourceId, null);
  7. } else {
  8. view = convertView;
  9. }
  10. ImageView fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
  11. TextView fruitName = (TextView) view.findViewById(R.id.fruit_name);
  12. fruitImage.setImageResource(fruit.getImageId());
  13. fruitName.setText(fruit.getName());
  14. return view;
  15. }

第二个參数就是我们最熟悉的convertView呀。难怪平时我们在写getView()方法是要推断一下convertView是不是等于null,假设等于null才调用inflate()方法来载入布局,不等于null就能够直接利用convertView,由于convertView就是我们之间利用过的View。仅仅只是被移出屏幕后进入到了废弃缓存中,如今又又一次拿出来使用而已。然后我们仅仅须要把convertView中的数据更新成当前位置上应该显示的数据。那么看起来就好像是全新载入出来的一个布局一样,这背后的道理你是不是已经全然搞明确了?

之后的代码又都是我们熟悉的流程了。从缓存中拿到子View之后再调用setupChild()方法将它又一次attach到ListView其中,由于缓存中的View也是之前从ListView中detach掉的,这部分代码就不再反复进行分析了。

为了方便大家理解,这里我再附上一张图解说明:

那么到眼下为止。我们就把ListView的整个工作流程代码基本分析结束了。文章比較长,希望大家能够理解清晰,下篇文章中会解说我们平时使用ListView时遇到的问题,感兴趣的朋友请继续阅读 Android ListView异步载入图片乱序问题。原因分析及解决方式 。

blockquote{
border-left: 10px solid rgba(128,128,128,0.075);
background-color: rgba(128,128,128,0.05);
border-radius: 0 5px 5px 0;
padding: 15px 20px;
}

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码即可关注:

        

Android ListView工作原理全然解析,带你从源代码的角度彻底理解的更多相关文章

  1. Android ListView工作原理完全解析,带你从源码的角度彻底理解

    版权声明:本文出自郭霖的博客,转载必须注明出处.   目录(?)[+] Adapter的作用 RecycleBin机制 第一次Layout 第二次Layout 滑动加载更多数据   转载请注明出处:h ...

  2. Android ListView工作原理完全解析(转自 郭霖老师博客)

    原文地址:http://blog.csdn.net/guolin_blog/article/details/44996879 在Android所有常用的原生控件当中,用法最复杂的应该就是ListVie ...

  3. Android 进阶学习:事件分发机制全然解析,带你从源代码的角度彻底理解(上)

    http://blog.csdn.net/guolin_blog/article/details/9097463 事实上我一直准备写一篇关于Android事件分发机制的文章,从我的第一篇博客開始,就零 ...

  4. android多线程-AsyncTask之工作原理深入解析(下)

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  5. android多线程-AsyncTask之工作原理深入解析(上)

    关联文章: Android 多线程之HandlerThread 完全详解 Android 多线程之IntentService 完全详解 android多线程-AsyncTask之工作原理深入解析(上) ...

  6. android handler工作原理

    android handler工作原理 作用 便于在子线程中更新主UI线程中的控件 这里涉及到了UI主线程和子线程 UI主线程 它很特别.通常我们会认为UI主线程将页面绘制完成,就结束了.但是它没有. ...

  7. Android 开源框架Universal-Image-Loader全然解析(二)--- 图片缓存策略具体解释

    转载请注明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/26810303),请尊重他人的辛勤劳动成果,谢谢! 本篇文章 ...

  8. 路由及路由器工作原理深入解析3:路由与port

        日志"路由及路由器工作原理深入解析1"http://user.qzone.qq.com/2756567163/blog/1438322342介绍了"为什么要使用路 ...

  9. NS域名工作原理及解析

    DNS域名工作原理及解析   0x00 定义 DNS( Domain Name System)是“域名系统”的英文缩写,它作为将域名和IP地址相互映射的一个分布式数据库,能够使人更方便地访问互联网.D ...

随机推荐

  1. centos6.5 403 Forbidden 设置了777还是不行

    Forbidden You don't have permission to access /liuyanben/install on this server. Apache/2.2.15 (Cent ...

  2. 基于tinkphp3.2获取openid

    <?php namespace Home\Controller; use Think\Controller; /** * 基础 */ class BaseController extends C ...

  3. C - 你经历过绝望吗?两次! 【地图型BFS+优先队列(障碍物)】

    4月16日,日本熊本地区强震后,受灾严重的阿苏市一养猪场倒塌,幸运的是,猪圈里很多头猪依然坚强存活.当地15名消防员耗时一天解救围困的“猪坚强”.不过与在废墟中靠吃木炭饮雨水存活36天的中国汶川“猪坚 ...

  4. HttpServletRequest接口

    package com.hongdian; import java.util.Enumeration; import java.io.IOException; import javax.servlet ...

  5. Maven学习笔记1

    Maven是什么? 百度百科:Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的软件项目管理工具. 这些描述总是让人更加难理解Maven,扔掉它,咱们先看看Mave ...

  6. noi 题库1.7字符串 第16至20题

    16:忽略大小写的字符串比较 一般我们用strcmp可比较两个字符串的大小,比较方法为对两个字符串从前往后逐个字符相比较(按ASCII码值大小比较),直到出现不同的字符或遇到'\0'为止.如果全部字符 ...

  7. 20、Django实战第20天:课程详情页

    1.把course-detail.html复制到templates目录下 2.编辑course-detail.html,分析页面,继承base.html 3.编辑courses.views .... ...

  8. order by 中利用 case when 排序

    order by case when a.ShopNumber in(1,2,3) then 0 else 1 end

  9. 【状压DP】旅行商问题

    给定一张带权有向图,要求从顶点0出发,经过每个结点恰好一次后再返回0,求边权和的最小值. 2<=n<=15 0<=d(i,j)<=1000 样例 5 80 1 30 3 41 ...

  10. hdu2829 四边形优化dp

    Lawrence Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total S ...