国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

ListView源碼分析

2019-11-09 18:45:39
字體:
來源:轉載
供稿:網友

1.概述

本文是閱讀 http://www.cnblogs.com/qiengo/p/3628235.html http://blog.csdn.net/iisPRing/article/details/50967445 http://blog.csdn.net/guolin_blog/article/details/44996879 http://www.jianshu.com/p/9c603a11b0c9 https://my.oschina.net/lorcan/blog/539215 的讀書筆記 代碼是 android N 的,記錄下方便記憶。

ListView 繼承結構 如下圖 這里寫圖片描述

AbsListView 的子類(ListView,GridView)都有RecyleBin機制 可以 防止 OOM,先來看看 RecyleBin,接著看 ListView 的原理(ListView 最終集成自 View,View的子類 主要看 onMeasure()、onLayout(), onDraw())

2. RecyleBin

2.1 概述

RecyleBin是view的回收站。

/** * The RecycleBin facilitates reuse of views across layouts. The RecycleBin has two levels of * storage: ActiveViews and ScrapViews. ActiveViews are those views which were onscreen at the * start of a layout. By construction, they are displaying current information. At the end of * layout, all views in ActiveViews are demoted to ScrapViews. ScrapViews are old views that * could potentially be used by the adapter to avoid allocating views unnecessarily. * 用于存儲不用的view,以便在下個layout中使用來避免創建新的 * @see android.widget.AbsListView#setRecyclerListener(android.widget.AbsListView.RecyclerListener) * @see android.widget.AbsListView.RecyclerListener */ class RecycleBin { private RecyclerListener mRecyclerListener; /** * The position of the first view stored in mActiveViews. */ private int mFirstActivePosition; /** * Views that were on screen at the start of layout. This array is populated at the start of * layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. * Views in mActiveViews represent a contiguous range of Views, with position of the first * view store in mFirstActivePosition. */ private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; ... }

ListView中有很多view,屏幕上能看到的view是onScreenView,是RecyleBin中的ActiveView,滑出屏幕的view是OffScreen的View,是RecyleBin中的ScrapView,scrap是廢棄的意思。

ListView把scrapView全部刪除,就不用繪制看不見的view了。ListView會把這些刪除的ScrapView放入到RecycleBin中存起來,就像把暫時無用的資源放到回收站一樣。

當ListView的底部需要顯示新的View的時候,會從RecycleBin中取出一個ScrapView,將其作為convertView參數傳遞給Adapter的getView方法,從而達到View復用的目的,這樣就不必在Adapter的getView方法中執行LayoutInflater.inflate()方法了。

RecyleBin 是防止 ListView 加載數據出現 OOM 的重要原因之一,RecyleBin 是 AbsListView 的內部類,RecycleBin的作用是幫助布局中的View的重用,它存儲了兩種類型的View:

mActiveViews 存儲的是OnScreen的View,這些View很有可能被直接復用mScrapViews 存儲的是OffScreen的View,這些View主要是用來間接復用的,這就是傳回getView中covertView的來源

2.2 RecycleBin變量

2.2.1 mRecyclerListener

RecyclerListener只有一個方法onMovedToScrapHeap(),addScrapView() 和 scrapActiveViews() 時,使用了該變量,如果注冊了RecyclerListener,就調用onMovedToScrapHeap(),表示該view不再顯示,這個view被回收到了scrap heap,該函數處理回收時view中的資源釋放。

2.2.2 mFirstActivePosition

The position of the first view stored in mActiveViews. 存儲在mActiveViews中的第一個view的位置,即AdapterView.getFirstVisiblePosition()返回值。

/** * Returns the position within the adapter's data set for the first item * displayed on screen. * * @return The position within the adapter's data set */ public int getFirstVisiblePosition() { return mFirstPosition; }

2.2.3 mActiveViews

Views that were on screen at the start of layout. This array is populated at the start of layout, and at the end of layout all view in mActiveViews are moved to mScrapViews. Views in mActiveViews represent a contiguous range of Views, with position of the first view store in mFirstActivePosition. 布局開始時屏幕顯示的view,這個數組會在布局開始時填充,布局結束后所有view被移至mScrapViews。

2.2.4 mScrapViews

Unsorted views that can be used by the adapter as a convert view. 這個ArrayList就是adapter中getView方法中的參數convertView的來源。注意:這里是一個數組,因為如果adapter中數據有多種類型,那么就會有多個ScrapViews

2.2.5 mViewTypeCount

view類型總數,列表中可能有多種數據類型,比如內容數據和分割符

2.2.6 mCurrentScrap

private ArrayList mCurrentScrap; 默認情況下,mCurrentScrap = scrapViews[0];

2.2.7 mTransientStateViews

If the data hasn’t changed, we can reuse the views at their old positions.

2.2.8 mTransientStateViewsById

If the adapter has stable IDs,we can reuse the view forthe same data.

2.2.9 mSkippedScrap

addScrapView()中,如果view不能添加到 mTransientStateViews,mTransientStateViewsById 中,就添加到 mSkippedScrap 中 Otherwise, we’ll have to remove the view and start over.

2.3 RecycleBin方法

2.3.1 markChildrenDirty()

為每個子類調用forceLayout()。將mScrapView中回收回來的View設置一樣標志,在下次被復用到ListView中時,告訴viewroot重新layout該view。forceLayout()方法只是設置標志,并不會通知其parent來重新layout。

public void markChildrenDirty() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; final int scrapCount = scrap.size(); for (int i = 0; i < scrapCount; i++) { scrap.get(i).forceLayout(); } } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; final int scrapCount = scrap.size(); for (int j = 0; j < scrapCount; j++) { scrap.get(j).forceLayout(); } } } if (mTransientStateViews != null) { final int count = mTransientStateViews.size(); for (int i = 0; i < count; i++) { mTransientStateViews.valueAt(i).forceLayout(); } } if (mTransientStateViewsById != null) { final int count = mTransientStateViewsById.size(); for (int i = 0; i < count; i++) { mTransientStateViewsById.valueAt(i).forceLayout(); } } }

2.3.2 shouldRecycleViewType()

判斷給定的view的viewType指明是否可以回收回。viewType < 0可以回收。指定忽略的( ITEM_VIEW_TYPE_IGNORE = -1),或者是 HeaderView / FootView(ITEM_VIEW_TYPE_HEADER_OR_FOOTER = -2)是不被回收的。如有特殊需要可以將自己定義的viewType設置為-1,否則,將會浪費內存,導致OOM

public boolean shouldRecycleViewType(int viewType) { return viewType >= 0; }

2.3.3 clear()

清空廢棄view堆,并將這些View從窗口中Detach

/** * Clears the scrap heap. */ void clear() { if (mViewTypeCount == 1) { final ArrayList<View> scrap = mCurrentScrap; clearScrap(scrap); } else { final int typeCount = mViewTypeCount; for (int i = 0; i < typeCount; i++) { final ArrayList<View> scrap = mScrapViews[i]; clearScrap(scrap); } } clearTransientStateViews(); }

2.3.4 fillActiveViews()

用AbsListView.的所有子view填充ActiveViews。從代碼看該方法的處理邏輯為將當前AbsListView的0-childCount個子類中的非header、footer的view添加到mActiveViews數組中。當Adapter中的數據個數未發生變化時(用戶滾動,或點擊等操作),ListView中item的個數會發生變化,因此,需要將可視的item加入到mActiveView中來管理

/** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount The minimum number of views mActiveViews should hold . mActiveViews應該保存的最少的view數 * @param firstActivePosition The position of the first view that will be stored in * mActiveViews. mActiveViews中存儲的首個view的位置,第一個可見的view的位置 */ void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; //noinspection MismatchedReadAndWriteOfArray final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state. lp.scrappedFromPosition = firstActivePosition + i; } } }

2.3.5 getActiveView()

獲取mActiveViews中指定位置的view,如果找到會將該view從mActiveViews中移除。position是adapter中的絕對下標值,mFirstActivePosition前面說過了,是當前可視區域的下標值,對應在adapter中的絕對值,如果找到,則返回找到的View,并將mActiveView對應的位置設置為null

/** * Get the view corresponding to the specified position. The view will be removed from * mActiveViews if it is found. * 根據position在mActiveViews中查找view * @param position The position to look up in mActiveViews * @return The view if it is found, null otherwise */ View getActiveView(int position) { int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; // 找到了把該view從mActiveViews中移除,mActiveViews中的View不能重復利用 return match; } return null; }

2.3.6 clearTransientStateViews()

清掉當前處于transient(瞬時)狀態的所有保存的view。內部為mTransientStateViews和mTransientStateViewsById的clear()調用

/** * Dumps and fully detaches any currently saved views with transient * state. */ void clearTransientStateViews() { final SparseArray<View> viewsByPos = mTransientStateViews; if (viewsByPos != null) { final int N = viewsByPos.size(); for (int i = 0; i < N; i++) { removeDetachedView(viewsByPos.valueAt(i), false); } viewsByPos.clear(); } final LongSparseArray<View> viewsById = mTransientStateViewsById; if (viewsById != null) { final int N = viewsById.size(); for (int i = 0; i < N; i++) { removeDetachedView(viewsById.valueAt(i), false); } viewsById.clear(); } }

2.3.7 addScrapView()

將view放入scrapview list中,有transient狀態的view不會被scrap(廢棄),會被加入mTransientStateViewsById,mTransientStateViews 或 調用getSkippedScrap() 加入mSkippedScrap中

/** * Puts a view into the list of scrap views. * <p> * If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * 緩存廢棄的view,RecycleBin當中使用 mScrapViews 和 mCurrentScrap 這兩個List來存儲廢棄View * @param scrap The view to add * scrap是要添加的view * * @param position The view's position within its parent * position是view在父類中的位置 */ void addScrapView(View scrap, int position) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams(); if (lp == null) { // Can't recycle, but we don't know anything about the view. // Ignore it completely. return; } // 設置它的scrappedFromPosition,然后從窗口中detach該view,并根據viewType加入到mScrapView中 lp.scrappedFromPosition = position; // Remove but don't scrap header or footer views, or views that // should otherwise not be recycled. final int viewType = lp.viewType; if (!shouldRecycleViewType(viewType)) { // Can't recycle. If it's not a header or footer, which have // special handling and should be ignored, then skip the scrap // heap and we'll fully detach the view later. if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { getSkippedScrap().add(scrap); } return; } scrap.dispatchStartTemporaryDetach(); // The the accessibility state of the view may change while temporary // detached and we do not allow detached views to fire accessibility // events. So we are announcing that the subtree changed giving a chance // to clients holding on to a view in this subtree to refresh it. notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap); } else { // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } else { if (mViewTypeCount == 1) { mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { // RecyclerListener可以通過AbsListView.setRecyclerListener()設置 mRecyclerListener.onMovedToScrapHeap(scrap); } } }

2.3.8 getScrapView()

/** * @return A view from the ScrapViews collection. These are unordered. * 從廢棄緩存( mCurrentScrap 或 mScrapViews )中取出一個View,廢棄緩存中的View是無序的 */ View getScrapView(int position) { final int whichScrap = mAdapter.getItemViewType(position); if (whichScrap < 0) { return null; } if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); // 從末尾返回一個View } else if (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } return null; }

2.3.9 retrieveFromScrap()

private View retrieveFromScrap(ArrayList<View> scrapViews, int position) { final int size = scrapViews.size(); if (size > 0) { // See if we still have a view for this position or ID. for (int i = 0; i < size; i++) { final View view = scrapViews.get(i); final AbsListView.LayoutParams params = (AbsListView.LayoutParams) view.getLayoutParams(); if (mAdapterHasStableIds) { final long id = mAdapter.getItemId(position); if (id == params.itemId) { return scrapViews.remove(i); } } else if (params.scrappedFromPosition == position) { // params.scrappedFromPosition == position,返回該view final View scrap = scrapViews.remove(i); clearAccessibilityFromScrap(scrap); return scrap; } } final View scrap = scrapViews.remove(size - 1);// 從末尾返回一個View clearAccessibilityFromScrap(scrap); return scrap; } else { // 緩存中沒有view,返回null return null; } }

返回null的情況舉例: ListView第一次加載完整屏item,此時mScrapView中是沒有緩存view的,新的view將會顯示,此時listview會調用Adapter.getView,但是緩存中沒有,因此convertView是null,所以,我們得分配一塊內存來創建新的convertView;

返回scrapViews最后一個view的情況: 接著上邊的情況,我們繼續向上滾動,第一個view完全移出屏幕(假設還沒有加載新的item),此時,第一個view就會被detach,并被加入到mScrapView中;然后,我們還繼續向上滾動,直接后面又將要顯示新的item view時,此時,系統會從mScrapView中找position對應的View,顯然是找不到的,則將從mScrapView中,取最后一個緩存的view傳遞給convertView

根據position返回view: 接著上邊的情況,第一個被完全移出 加入到mScrapView中,假設此時還沒有新增的item到listview中,此時緩存中就只有第一個view;然后,現在向下滑動,則之前的第一個item,將被顯示出來,此時,從緩存中查找position對應的view有沒有,當然,肯定是找到了,就直接返回了

2.3.10 removeSkippedScrap()

清空 mSkippedScrap

/** * Finish the removal of any views that skipped the scrap heap. */ void removeSkippedScrap() { if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); }

2.3.11 scrapActiveViews()

Move all views remaining in mActiveViews to mScrapViews. 將mActiveView中未使用的view回收(因為,此時已經移出可視區域了)。會調用mRecyclerListener.onMovedToScrapHeap(scrap);回收view的資源

2.3.12 pruneScrapViews()

scrapActiveViews()調用了該方法, pruneScrapViews() 確保mScrapViews 的數目不會超過mActiveViews的數目 (This can happen if an adapter does not recycle its views)。 mScrapView中每個ScrapView數組大小不應該超過mActiveView的大小,如果超過,系統認為程序并沒有復用convertView,而是每次都是創建一個新的view,為了避免產生大量的閑置內存且增加OOM的風險,系統會在每次回收后,去檢查一下,將超過的部分釋放掉,節約內存降低OOM風險。

/** * Makes sure that the size of mScrapViews does not exceed the size of * mActiveViews, which can happen if an adapter does not recycle its * views. Removes cached transient state views that no longer have * transient state. */

2.3.13 reclaimScrapViews()

將mScrapView中所有的緩存view全部添加到指定的view list中,只看到有AbsListView.reclaimViews有調用到,但沒有其它方法使用這個函數,可能在特殊情況下會使用到,但目前從framework中,看不出來。

/** * Puts all views in the scrap heap into the supplied list. */ void reclaimScrapViews(List<View> views) { if (mViewTypeCount == 1) { views.addAll(mCurrentScrap); } else { final int viewTypeCount = mViewTypeCount; final ArrayList<View>[] scrapViews = mScrapViews; for (int i = 0; i < viewTypeCount; ++i) { final ArrayList<View> scrapPile = scrapViews[i]; views.addAll(scrapPile); } } }

2.3.14 setCacheColorHint()

Updates the cache color hint of all known views. 更新view的緩存顏色提示setDrawingCacheBackgroundColor。為所有的view繪置它們的背景色。

3.加載第一屏,第一次 layout

界面顯示一個view,經過三個階段:onMeasure()->onLayout()->onDraw()

onMeasure()用于測量View的大小,占用的大小通常是整個屏幕onDraw()用于將View繪制到界面上onLayout()用于確定View的布局

AdapterView繼承自ViewGroup,ViewGroup通過addView()添加View,AdapterView重寫了addView()方法,禁用了該方法: android/widget/AdapterView.java

/** * This method is not supported and throws an UnsupportedOperationException when called. * * @param child Ignored. * * @throws UnsupportedOperationException Every time this method is invoked. */ @Override public void addView(View child) { throw new UnsupportedOperationException("addView(View) is not supported in AdapterView"); }

AdapterView把addView方法給禁用了,那么ListView怎么向其中添加child呢?通過onLayout中調用layoutChildren(),AbsListView的主要實現也在 onLayout中,

layoutChildren()關于RecyleBin主要干了3件事:

ListView的children放到RecycleBin中ListView清空childrenRecycleBin中緩存的view復用,變成ListView的children

下面來看 onLayout 流程 這里寫圖片描述

3.1 AbsListView.onLayout()

/** * Subclasses should NOT override this method but * {@link #layoutChildren()} instead. * 做了2件事: * 1.判斷 changed,如果ListView的大小或者位置發生了變化,子布局重繪 * 2.調用子類的 layoutChildren() */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mInLayout = true; final int childCount = getChildCount(); if (changed) { // 如果變化了(大小或位置),子布局重繪 for (int i = 0; i < childCount; i++) { getChildAt(i).forceLayout(); } mRecycler.markChildrenDirty(); } layoutChildren(); // 由子類實現 mInLayout = false; mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; // TODO: Move somewhere sane. This doesn't belong in onLayout(). if (mFastScroll != null) { mFastScroll.onItemCountChanged(getChildCount(), mItemCount); } } /** * Subclasses must override this method to layout their children. */ protected void layoutChildren() { }

onLayout() 代碼不多,判斷 changed,如果ListView的大小或者位置發生了變化,子布局重繪,調用子類的 layoutChildren()

3.2 ListView.layoutChildren()

@Override protected void layoutChildren() { ... final int childCount = getChildCount();// 第一次加載時,ListView中還沒有item,getChildCount() 返回0 ... // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition;// //mFirstPosition是ListView的成員變量,存儲著第一個顯示的child所對應的adapter的position final RecycleBin recycleBin = mRecycler; // 如果Adapter調用了notifyDataSetChanged方法,那么AdapterView就會知道Adapter的數據源發生了變化,此時dataChanged變量就為true if (dataChanged) { for (int i = 0; i < childCount; i++) { // 這里如果數據發生了改變,將把所有現在的child放到scrapView中 recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // 如果數據沒有發生改變,將把所有現在的child放到activeViews中 // 數據沒有變化時,走這里 緩存子view,第一次加載時 ListView中沒有子view,該行不起作用 recycleBin.fillActiveViews(childCount, firstPosition); } // Clear out old views detachAllViewsFromParent(); recycleBin.removeSkippedScrap(); // 根據不同情況 增刪子view,這些case的代碼邏輯大部分最終調用了fillDown、fillUp等方法 // mLayoutMode的默認值是 LAYOUT_NORMAL, 走 default switch (mLayoutMode) { ... default: if (childCount == 0) { // 第一次進入時 childCount = 0 if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); sel = fillFromTop(childrenTop); } else { ... } } else { ... } break; } // Flush any cached views that did not get reused above recycleBin.scrapActiveViews();//廢棄activeViews,會把activeview給廢棄并移入scrapview中 ... }

layoutChildren() 調用 fillFromTop() 加載 item 的布局

3.3 ListView.fillFromTop()

/** * Fills the list from top to bottom, starting with mFirstPosition * 從 mFirstPosition開始,從上到下 填充 * @param nextTop The location where the top of the first item should be * drawn. 第一個子元素頂部距離整個ListView頂部的像素值 * * @return The view that is currently selected */ private View fillFromTop(int nextTop) { mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); if (mFirstPosition < 0) { mFirstPosition = 0; } // 確定 mFirstPosition,調用 fillDown() return fillDown(mFirstPosition, nextTop); }

fillFromTop() 確定 mFirstPosition,調用 fillDown(),沒做具體加載 子布局的工作,來看 fillDown()

3.4 ListView.fillDown()

fillDown用子View從指定的position自上而下填充ListView,,fillUp則是自下而上填充

/** * Fills the list from pos down to the end of the list view. * * @param pos The first position to put in the list * pos 表示列表中第一個要繪制的item的position,其對應著Adapter中的索引 * * @param nextTop The location where the top of the item associated with pos * should be drawn * nextTop表示第一個要繪制的item在ListView中實際的位置, * * @return The view that is currently selected, if it happens to be in the * range that we draw. */ private View fillDown(int pos, int nextTop) { View selectedView = null; int end = (mBottom - mTop); // end是ListView底部減去頂部所得的像素值,是ListView的高度 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } // nextTop 是下一個子item的頭部,end是listveiw的底部的高度, // 比較nextTop 和 end,判斷是否繼續填充下一個item,nextTop < end確保了我們只要將新增的子View能夠覆蓋ListView的界面,nextTop >= end 時,子元素超出屏幕 // mItemCount 是Adapter 元素的數量,pos < mItemCount確保了我們新增的子View在Adapter中都有對應的數據源item,pos >= mItemCount 時,adapter元素都被遍歷完了 while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; // 將pos和nextTop傳遞給makeAndAddView方法 View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); // child的bottom值表示的是該child的底部到ListView頂部的距離,將該child的bottom作為下一個child的top,nextTop保存著下一個child的top值 nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; // 循環一次,pos加1,position指針下移 } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }

fillDown() 是加載item 布局的主要實現,while循環 保證了 只加載一屏,超出的數據不會加載

nextTop < end確保了我們只要將新增的子View能夠覆蓋ListView的界面就可以了,比如ListView的高度最多顯示10個子View,我們沒必要向ListView中加入11個子View。pos < mItemCount確保了我們新增的子View在Adapter中都有對應的數據源item,比如ListView的高度最多顯示10個子View,但是我們Adapter中一共才有5條數據,這種情況下只能向ListView中加入5個子View,從而不能填充滿ListView的全部高度。

再看看 每個item的 布局 是怎么加載的

3.5 ListView.makeAndAddView()

該方法會創建View, 返回這個View作為child,,并把該子View添加到ListView的children中

/** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * position表示的是數據源item在Adapter中的索引 * * @param y Top or bottom edge of the view to add * y表示要生成的View的top值或bottom值 * * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * flow是true,那么y表示top值,否則表示bottom值 * * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // 如果數據沒有變化,嘗試用該position從RecycleBin的mActiveViews中獲取可復用的View // Try to use an existing view for this position child = mRecycler.getActiveView(position); // 第一次進入時 ,child 是null if (child != null) { // 如果child 不為空,說明我們找到了一個已經存在的child, // 這樣mActiveViews中存儲的View就被直接復用了 // 調用setupChild,對child進行定位 // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // 如果數據變化了,新建一個View,或者如果可能的話去ScrapView中拿一個緩存的view // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // 這個方法負責進行定位和量算,把View放到ListView中合適的位置 // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

makeAndAddView() 有兩種方式加載一個view,一個是 mRecycler.getActiveView(),一個是 obtainView() 第一次加載時,activeview僅僅在第一次layoutChildren中賦值且為空值,所以走 obtainView() 加載了 一個View,加載完的View傳入 setupChild() 來顯示

3.6 AbsListView.obtainView()

如果沒能夠從mActivieViews中直接復用View,那么就要調用obtainView方法獲取View,該方法嘗試間接復用RecycleBin中的mScrapViews中的View,如果不能間接復用,則創建新的View。

/** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if * the returned view was taken from the "temporary detached" scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { ... return transientView; } final View scrapView = mRecycler.getScrapView(position); // 嘗試獲取一個廢棄緩存中的View final View child = mAdapter.getView(position, scrapView, this); // 調用了getView方法,去初始化我們的covertView // 第一次加載時,scrapView = null ... }

obtainView() 要返回一個View,這個view可能是從廢棄緩存或 Adapter的getView()中獲取的,第一次加載時 mRecycler.getTransientStateView()返回值是null,所以obtainView() 返回的是view是 mAdapter.getView()

獲取到view,makeAndAddView() 接著往下執行,調用 setupChild(),obtainView() 返回的View也傳入 setupChild() 中

3.7 ListView.setupChild()

/** * Add a view as a child and make sure it is measured (if necessary) and * positioned properly. * * @param child The view to add * @param position The position of this child * @param y The y position relative to which this view will be positioned * @param flowDown If true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @param recycled Has this view been pulled from the recycle bin? If so it * does not need to be remeasured. */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ... addViewInLayout(child, flowDown ? -1 : 0, p, true); ... }

setupChild() 調用 ViewGroup.addViewInLayout() 顯示 這個item的View

3.8 ViewGroup.addViewInLayout()

/** * Adds a view during layout. This is useful if in your onLayout() method, * you need to add more views (as does the list view for example). * * If index is negative, it means put it at the end of the list. * * @param child the view to add to the group * @param index the index at which the child must be added or -1 to add last * @param params the layout parameters to associate with the child * @param preventRequestLayout if true, calling this method will not trigger a * layout request on child * @return true if the child was added, false otherwise */ protected boolean addViewInLayout(View child, int index, LayoutParams params, boolean preventRequestLayout) { if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } child.mParent = null; addViewInner(child, index, params, preventRequestLayout); child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; return true; }

3.9 AbsListView.RecyleBin.scrapActiveViews()

加載完view,ListView.layoutChildren() 都到了 RecyleBin.scrapActiveViews()

/** * Move all views remaining in mActiveViews to mScrapViews. * 將mActiveViews廢棄到mScrapViews中 */ void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayList<View> scrapViews = mCurrentScrap; final int count = activeViews.length; for (int i = count - 1; i >= 0; i--) { final View victim = activeViews[i]; if (victim != null) { final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) victim.getLayoutParams(); final int whichScrap = lp.viewType; activeViews[i] = null; if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim);// 將 victim 添加到 mTransientStateViewsById 中 } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(mFirstActivePosition + i, victim); // 將 victim 添加到 mTransientStateViews 中 } ... }

RecyleBin.scrapActiveViews()中有2個重要的賦值操作,是將view添加到 瞬間狀態的數組中,瞬態的來源是view中hasTransientState方法,View.hasTransientState() 如下,其方法作用看源碼解釋就行,item中如果有view正在進行動畫之類的動態改變發生。

/** * Indicates whether the view is currently tracking transient state that the * app should not need to concern itself with saving and restoring, but that * the framework should take special note to preserve when possible. * * <p>A view with transient state cannot be trivially rebound from an external * data source, such as an adapter binding item views in a list. This may be * because the view is performing an animation, tracking user selection * of content, or similar.</p> * * @return true if the view has transient state */ @ViewDebug.ExportedProperty(category = "layout") public boolean hasTransientState() { return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE; }

第一次layoutchildern 結束了

4.加載第二屏,第二次 layout

這里寫圖片描述

4.1 ListView.layoutChildren()

@Override protected void layoutChildren() { ... final int childCount = getChildCount(); // 第一次加載時,ListView中還沒有item,getChildCount() 返回0 // 第二次加載時,getChildCount() = 一屏可以顯示的item的個數 ... // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // 數據沒有變化時,走這里 緩存子view,第一次加載時 ListView中沒有子view,該行不起作用 // 第二次加載時,ListView中已經有子view了,會緩存到 RecyleBin 的 mActiveViews 數組中 recycleBin.fillActiveViews(childCount, firstPosition); } // Clear out old views detachAllViewsFromParent(); // 將所有ListView中的子View清除掉,保證第二次layout不會產生重復數據,從 RecyleBin中取緩存的子View,不會重新inflate recycleBin.removeSkippedScrap();// //將子view和listview給detach掉 // 默認的布局模式是 LAYOUT_NORMAL, 走 default switch (mLayoutMode) { ... default: if (childCount == 0) { // 第一次進入時 childCount = 0 if (!mStackFromBottom) { final int position = lookForSelectablePosition(0, true); setSelectedPositionInt(position); sel = fillFromTop(childrenTop); } else { final int position = lookForSelectablePosition(mItemCount - 1, false); setSelectedPositionInt(position); sel = fillUp(mItemCount - 1, childrenBottom); } } else { // 第二次 childCount != 0 if (mSelectedPosition >= 0 && mSelectedPosition < mItemCount) { // 默認情況,我們沒有選中子view,不走該分支 sel = fillSpecific(mSelectedPosition, oldSel == null ? childrenTop : oldSel.getTop()); } else if (mFirstPosition < mItemCount) { // mFirstPosition一開始是0,只要Adapter中數據大于0,該條件就成立了 sel = fillSpecific(mFirstPosition, oldFirst == null ? childrenTop : oldFirst.getTop()); } else { sel = fillSpecific(0, childrenTop); } } break; } ... }

第二次加載時 recycleBin.fillActiveViews() 給activeview添加數據。將第一次加載的view放在了activeViews中 detachAllViewsFromParent() 清除掉 ListView中的子View,防止數據重復,ListView中所有的子View都是處于detach狀態,后續從RecyleBin中獲取緩存的view 進入switch (mLayoutMode),LayoutMode還是LAYOUT_NORMAL,進入default分支,getChildCount() 不再是0了,是 一屏顯示的item的個數 調用 fillSpecific() 加載view

4.2 recycleBin.fillActiveViews()

/** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount The minimum number of views mActiveViews should hold * @param firstActivePosition The position of the first view that will be stored in * mActiveViews */ void fillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = new View[childCount]; } mFirstActivePosition = firstActivePosition; //noinspection MismatchedReadAndWriteOfArray final View[] activeViews = mActiveViews; for (int i = 0; i < childCount; i++) { View child = getChildAt(i); AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams(); // Don't put header or footer views into the scrap heap if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in active views. // However, we will NOT place them into scrap views. activeViews[i] = child; // Remember the position so that setupChild() doesn't reset state. lp.scrappedFromPosition = firstActivePosition + i; } } }

4.3 ViewGroup.detachAllViewsFromParent():

將所有的子View從ListView中分離,也就是清空了children

/** * Detaches all views from the parent. Detaching a view should be followed * either by a call to * {@link #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams)} * or a call to {@link #removeDetachedView(View, boolean)}. Detachment should only be * temporary; reattachment or removal should happen within the same drawing cycle as * detachment. When a view is detached, its parent is null and cannot be retrieved by a * call to {@link #getChildAt(int)}. * * @see #detachViewFromParent(View) * @see #detachViewFromParent(int) * @see #detachViewsFromParent(int, int) * @see #attachViewToParent(View, int, android.view.ViewGroup.LayoutParams) * @see #removeDetachedView(View, boolean) */ protected void detachAllViewsFromParent() { final int count = mChildrenCount; if (count <= 0) { return; } final View[] children = mChildren; mChildrenCount = 0; for (int i = count - 1; i >= 0; i--) { children[i].mParent = null; children[i] = null; } }

4.4 recycleBin.removeSkippedScrap()

/** * Finish the removal of any views that skipped the scrap heap. */ void removeSkippedScrap() { if (mSkippedScrap == null) { return; } final int count = mSkippedScrap.size(); for (int i = 0; i < count; i++) { removeDetachedView(mSkippedScrap.get(i), false); } mSkippedScrap.clear(); }

4.5 ListView.fillSpecific()

/** * Put a specific item at a specific location on the screen and then build * up and down from there. * * @param position The reference view to use as the starting point * @param top Pixel offset from the top of this view to the top of the * reference view. * * @return The selected view, or null if the selected view is outside the * visible area. */ private View fillSpecific(int position, int top) { boolean tempIsSelected = position == mSelectedPosition; View temp = makeAndAddView(position, top, true, mListPadding.left, tempIsSelected); // Possibly changed again in fillUp if we add rows above this one. mFirstPosition = position; View above;// above為頭部item View below;// below為底部的item final int dividerHeight = mDividerHeight; if (!mStackFromBottom) { above = fillUp(position - 1, temp.getTop() - dividerHeight); // This will correct for the top of the first view not touching the top of the list adjustViewsUpOrDown(); below = fillDown(position + 1, temp.getBottom() + dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooHigh(childCount); } } else { below = fillDown(position + 1, temp.getBottom() + dividerHeight); // This will correct for the bottom of the last view not touching the bottom of the list adjustViewsUpOrDown(); above = fillUp(position - 1, temp.getTop() - dividerHeight); int childCount = getChildCount(); if (childCount > 0) { correctTooLow(childCount); } } if (tempIsSelected) { return temp; } else if (above != null) { return above; } else { return below; } }

fillSpecific()方法會優先將指定位置的子View先加載到屏幕上,然后再加載該子View往上以及往下的其它子View。那么由于這里我們傳入的position就是第一個子View的位置,于是fillSpecific()方法的作用就基本上和fillDown()方法是差不多的了,接著看 makeAndAddView()

4.6 ListView.makeAndAddView()

/** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position);// 從 RecyleBin 中獲取一個active view // 第一次進入時 ,child 是null // 第二次進入時,getActiveView()會返回一個View,child != null if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

layoutChildren() 中調用 recycleBin.fillActiveViews 緩存了子View makeAndAddView() 中 調用 mRecycler.getActiveView() 取出緩存的子View 調用 setupChild(),最后一個參數 是true,表示是從RecyleBin中取出的緩存的view 第二次 layout時,不走obtainView(),會節省一些時間

4.7 RecyleBin.getActiveView()

舉一個例子,假設在某一時刻ListView中顯示了10個子View,position依次為從0到9。然后我們手指向上滑動,且向上滑動了一個子View的高度,ListView需要繪制下一幀。這時候ListView在layoutChildren方法中把這10個子View都放入到了RecycleBin的mActiveViews數組中了,然后清空了children數組,然后調用fillDown方法,向ListView中依次添加position1到10的子View,在添加position為1的子View的時候,由于在上一幀中position為1的子View已經被放到mActiveViews數組中了,這次直接可以將其從mActiveViews數組中取出來,這樣就是直接復用子View,所以說RecycleBin的mActiveViews數組主要是用于直接復用的。

/** * Get the view corresponding to the specified position. The view will be removed from * mActiveViews if it is found. * * @param position The position to look up in mActiveViews * @return The view if it is found, null otherwise */ View getActiveView(int position) { int index = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; // 將activeview的對應item給置空,說明activeview僅僅只能使用一次 return match; } return null; }

4.8 ListView.setupChild()

/** * Add a view as a child and make sure it is measured (if necessary) and * positioned properly. * * @param child The view to add * @param position The position of this child * @param y The y position relative to which this view will be positioned * @param flowDown If true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @param recycled Has this view been pulled from the recycle bin? If so it * does not need to be remeasured.第二次加載時,recycled = true */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ... if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) { // 第二次加載時調用 attachViewToParent(child, flowDown ? -1 : 0, p); } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } // 第一次加載時調用 addViewInLayout(child, flowDown ? -1 : 0, p, true); } ... }

setupChild() 調用 attachViewToParent() ,將view添加到 viewGroup中

4.9 ViewGroup.attachViewToParent()

/** * Attaches a view to this view group. Attaching a view assigns this group as the parent, * sets the layout parameters and puts the view in the list of children so that * it can be retrieved by calling {@link #getChildAt(int)}. * <p> * This method is intended to be lightweight and makes no assumptions about whether the * parent or child should be redrawn. Proper use of this method will include also making * any appropriate {@link #requestLayout()} or {@link #invalidate()} calls. * For example, callers can {@link #post(Runnable) post} a {@link Runnable} * which performs a {@link #requestLayout()} on the next frame, after all detach/attach * calls are finished, causing layout to be run prior to redrawing the view hierarchy. * <p> * This method should be called only for views which were detached from their parent. * * @param child the child to attach * @param index the index at which the child should be attached * @param params the layout parameters of the child * * @see #removeDetachedView(View, boolean) * @see #detachAllViewsFromParent() * @see #detachViewFromParent(View) * @see #detachViewFromParent(int) */ protected void attachViewToParent(View child, int index, LayoutParams params) { child.mLayoutParams = params; if (index < 0) { index = mChildrenCount; } addInArray(child, index); child.mParent = this; child.mPrivateFlags = (child.mPrivateFlags & ~PFLAG_DIRTY_MASK & ~PFLAG_DRAWING_CACHE_VALID) | PFLAG_DRAWN | PFLAG_INVALIDATED; this.mPrivateFlags |= PFLAG_INVALIDATED; if (child.hasFocus()) { requestChildFocus(child, child.findFocus()); } dispatchVisibilityAggregated(isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown()); }

5.AbsListView.obtainView()

5.1 AbsListView.obtainView()

/** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if * the returned view was taken from the "temporary detached" scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this); // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } } isScrap[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; } // trackMotionScroll()中,一旦有子view移除屏幕,就加入到廢棄緩存中 final View scrapView = mRecycler.getScrapView(position); // 嘗試獲取一個廢棄緩存中的View final View child = mAdapter.getView(position, scrapView, this); // 通過 Adapter.getView() 獲取一個 View // 第一次加載時,scrapView = null if (scrapView != null) { // 如果不相等,這種情況就是我們寫Adapter.getView()的covertView為null的情況 if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else { if (child.isTemporarilyDetached()) { isScrap[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } else { // we set isScrap to "true" only if the view is temporarily detached. // if the view is fully detached, it is as good as a view created by the // adapter isScrap[0] = false; } } } ... return child; }

5.2 RecyclerBin.getTransientStateView()

View getTransientStateView(int position) { if (mAdapter != null && mAdapterHasStableIds && mTransientStateViewsById != null) { long id = mAdapter.getItemId(position); View result = mTransientStateViewsById.get(id);// 從 mTransientStateViewsById 中返回view mTransientStateViewsById.remove(id); return result; } if (mTransientStateViews != null) { final int index = mTransientStateViews.indexOfKey(position); if (index >= 0) { View result = mTransientStateViews.valueAt(index); // 從 mTransientStateViews 中返回view mTransientStateViews.removeAt(index); return result; } } return null; }

getTransientStateView() 從 mTransientStateViewsById 或 mTransientStateViews 返回一個view

5.3 瞬態的view是從哪來的

mTransientStateViewsById , mTransientStateViews 在哪賦值的 scrapActiveViews() 和 addScrapView() 中

AbsListView.RecyleBin.scrapActiveViews()

/** * Move all views remaining in mActiveViews to mScrapViews. * 將mActiveViews廢棄到mScrapViews中 */ void scrapActiveViews() { ... if (victim.hasTransientState()) { // Store views with transient state for later use. victim.dispatchStartTemporaryDetach(); if (mAdapter != null && mAdapterHasStableIds) { if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<View>(); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim);// 將 victim 添加到 mTransientStateViewsById 中 } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<View>(); } mTransientStateViews.put(mFirstActivePosition + i, victim); // 將 victim 添加到 mTransientStateViews 中 } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view. removeDetachedView(victim, false); } } ... }

AbsListView.RecyleBin.addScrapView()

/** * Puts a view into the list of scrap views. * <p> * If the list data hasn't changed or the adapter has stable IDs, views * with transient state will be preserved for later retrieval. * * @param scrap The view to add * @param position The view's position within its parent */ void addScrapView(View scrap, int position) { ... // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { if (mAdapter != null && mAdapterHasStableIds) { // If the adapter has stable IDs, we can reuse the view for // the same data. if (mTransientStateViewsById == null) { mTransientStateViewsById = new LongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap);//將 scrap添加到 mTransientStateViewsById 中 } else if (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = new SparseArray<>(); } mTransientStateViews.put(position, scrap);// 將 scrap 添加到 mTransientStateViews 中 } else { // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } ... }

5.4 添加 view 的依據:View.hasTransientState()

/** * Indicates whether the view is currently tracking transient state that the * app should not need to concern itself with saving and restoring, but that * the framework should take special note to preserve when possible. * * <p>A view with transient state cannot be trivially rebound from an external * data source, such as an adapter binding item views in a list. This may be * because the view is performing an animation, tracking user selection * of content, or similar.</p> * * @return true if the view has transient state */ @ViewDebug.ExportedProperty(category = "layout") public boolean hasTransientState() { return (mPrivateFlags2 & PFLAG2_HAS_TRANSIENT_STATE) == PFLAG2_HAS_TRANSIENT_STATE; }

transient state類似 view 的一個屬性,該屬性的作用就是標注當前view是否正在變化中。 ListView的item 在加載數據時或者 播放動畫時,view處于 瞬態 transient state。

obtainView()中final View updatedView = mAdapter.getView(position, transientView, this); mAdapter.getView() 獲得的view是靜態的view,沒有發生任何動態改變和展現任何正在發生中的動畫的item。 接著下邊 mRecycler.addScrapView(updatedView, position);添加到RecyleBin中的是這個靜態的view

/** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if * the returned view was taken from the "temporary detached" scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this);// 獲取一個靜態的view // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position);// 將這個靜態的view添加到RecyleBin中 } } isScrap[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; } ... }

6.滑動加載更多

上邊介紹了 怎么加載數據,下邊將是 ListView最神奇的部分,滑動加載更多, 比如說我們的Adapter當中有1000條數據,但是第一屏只顯示了10條,ListView中也只有10個子View而已,那么剩下的990是怎樣工作并顯示到界面上的呢? ListView 和 GridView 都支持 滑動加載數據,所以滑動的代碼在 AbsListView 中 onTouchEvent() 中代碼非常多,邏輯也復雜 滑動部分對應的 ACTION_MOVE,我們看 ACTION_MOVE 的邏輯 這里寫圖片描述

6.1 AbsListView.onTouchEvent()

@Override public boolean onTouchEvent(MotionEvent ev) { ... switch (actionMasked) { ... case MotionEvent.ACTION_MOVE: { onTouchMove(ev, vtev); break; } ... } }

ACTION_MOVE 調用了 onTouchMove()

6.2 AbsListView.onTouchMove()

private void onTouchMove(MotionEvent ev, MotionEvent vtev) { ... // 滑動時對應的 touchMode 是 TOUCH_MODE_SCROLL switch (mTouchMode) { ... case TOUCH_MODE_SCROLL: case TOUCH_MODE_OVERSCROLL: scrollIfNeeded((int) ev.getX(pointerIndex), y, vtev); break; } }

手指上滑時,TouchMode是TOUCH_MODE_SCROLL,onTouchMove() 調用了 scrollIfNeeded()

6.3 AbsListView.scrollIfNeeded()

private void scrollIfNeeded(int x, int y, MotionEvent vtev) { ... if (incrementalDeltaY != 0) { atEdge = trackMotionScroll(deltaY, incrementalDeltaY); // 跟蹤移動,滑動屏幕時,trackMotionScroll() 會被多次調用 } ... }

scrollIfNeeded() 中 屏幕有移動就會調用 trackMotionScroll(),滑動屏幕時,trackMotionScroll() 會被多次調用

6.4 AbsListView.trackMotionScroll()

/** * Track a motion scroll * * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * deltaY 表示 從手指按下時的位置 到 當前手指位置的距離 * @param incrementalDeltaY Change in deltaY from the previous event. * incrementalDeltaY 表示據上次觸發event事件 手指在 y方向上的改變量,通過正負值判斷向上或向下滑動 * @return true if we're already at the beginning/end of the list and have nothing to do. */ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { ... // incrementalDeltaY < 0 是向下滑動,否則是向上滑動 if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } ... final boolean down = incrementalDeltaY < 0; ... if (down) { // 手指上滑 int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { // 循環 從上往下依次獲取子view final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { // 子view已經移出屏幕了 count++; // count 用于記錄有多少個子View被移出了屏幕 int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position);// 將該view加入到廢棄緩存中 } } } } else { // 手指下滑 int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { // 從下往上 依次獲取子view final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { // 子view已經移出屏幕了 start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count);// 把移出屏幕的子view全detach掉,ListView中看不到的就不保存了,畢竟有那么多數據等著加載 mRecycler.removeSkippedScrap(); } // invalidate before moving the children to avoid unnecessary invalidate // calls to bubble up from the children all the way to the top if (!awakenScrollBars()) { invalidate(); } offsetChildrenTopAndBottom(incrementalDeltaY);// 所有的子View按照 incrementalDeltaY 進行偏移,實現了隨著手指拖動,ListView隨著滾動的效果 if (down) { mFirstPosition += count; } final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { // ListView的最后一個子View的底部已經移入屏幕 或者 第一個子View的頂部已經移入屏幕 fillGap(down); } ... }

通過 down 來判斷 上滑 或下滑,遍歷子View,移出屏幕的子View 通過 mRecycler.addScrapView() 添加到 RecyleBin的廢棄緩存中 detachViewsFromParent() ListView 中移出屏幕的子view全detach掉 offsetChildrenTopAndBottom() 使view隨著 incrementalDeltaY 偏移,實現ListView隨著手指動的效果 listView滑到頭了,調用 fillGap(),fillGap() 是一個抽象方法,由子類實現

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

6.5 ListView.fillGap()

/** * {@inheritDoc} */ @Override void fillGap(boolean down) { final int count = getChildCount(); if (down) { // 手指上滑 int paddingTop = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingTop = getListPaddingTop(); } final int startOffset = count > 0 ? getChildAt(count - 1).getBottom() + mDividerHeight : paddingTop; fillDown(mFirstPosition + count, startOffset); correctTooHigh(getChildCount()); } else { // 手指下滑 int paddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { paddingBottom = getListPaddingBottom(); } final int startOffset = count > 0 ? getChildAt(0).getTop() - mDividerHeight : getHeight() - paddingBottom; fillUp(mFirstPosition - 1, startOffset); correctTooLow(getChildCount()); } }

fillGap() 中調用 fillDown() 或 fillUp()

6.6 ListView.fillDown() 或 fillUp()

/** * Fills the list from pos down to the end of the list view. * * @param pos The first position to put in the list * * @param nextTop The location where the top of the item associated with pos * should be drawn * * @return The view that is currently selected, if it happens to be in the * range that we draw. */ private View fillDown(int pos, int nextTop) { View selectedView = null; int end = (mBottom - mTop); // end是ListView底部減去頂部所得的像素值 if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } // mItemCount 是Adapter 元素的數量, nextTop >= end 時,子元素超出屏幕,pos >= mItemCount 時,adapter元素都被遍歷完了 while (nextTop < end && pos < mItemCount) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextTop, true, mListPadding.left, selected); nextTop = child.getBottom() + mDividerHeight; if (selected) { selectedView = child; } pos++; // 循環一次,pos加1 } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; } /** * Fills the list from pos up to the top of the list view. * * @param pos The first position to put in the list * * @param nextBottom The location where the bottom of the item associated * with pos should be drawn * * @return The view that is currently selected */ private View fillUp(int pos, int nextBottom) { View selectedView = null; int end = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end = mListPadding.top; } while (nextBottom > end && pos >= 0) { // is this the selected item? boolean selected = pos == mSelectedPosition; View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected); nextBottom = child.getTop() - mDividerHeight; if (selected) { selectedView = child; } pos--; } mFirstPosition = pos + 1; setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }

fillDown() 和 fillUp() 都調用了 makeAndAddView()

6.7 ListView.makeAndAddView()

/** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position);// 從 RecyleBin 中獲取一個active view // 第一次進入時 ,child 是null // 第二次進入時,getActiveView()會返回一個View,child != null // 根據RecycleBin的機制,mActiveViews是不能夠重復利用的, 第二次加載時 獲取過,再調用 getActiveView 返回的是 null if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

makeAndAddView() 中 調用 mRecycler.getActiveView() 返回的是 null (因為第二次加載時,已經調用了 getActiveView(), mActiveViews是不能夠重復利用的) 調用 obtainView(),setupChild()

6.8 AbsListView.obtainView()

/** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if * the returned view was taken from the "temporary detached" scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { ... // trackMotionScroll()中,一旦有子view移除屏幕,就加入到廢棄緩存中 final View scrapView = mRecycler.getScrapView(position); // 嘗試獲取一個廢棄緩存中的View final View child = mAdapter.getView(position, scrapView, this); // 通過 Adapter.getView() 獲取一個 View // 第一次加載時,scrapView = null if (scrapView != null) { if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else { if (child.isTemporarilyDetached()) { isScrap[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } else { // we set isScrap to "true" only if the view is temporarily detached. // if the view is fully detached, it is as good as a view created by the // adapter isScrap[0] = false; } } } ... }

mRecycler.getScrapView() 從廢棄緩存中獲取一個view 從緩存中拿到子View之后再調用setupChild()方法將它重新attach到ListView當中

getView()例子

public View getView(int position, View convertView, ViewGroup parent){ ViewHolder holder; if (convertView == null) { convertView = mInflater.inflate(R.layout.list_item, parent, false); holder = new ViewHolder(); holder.text = (TextView)convertView.findViewById(R.id.text); holder.icon = (ImageView)convertView.findViewById(R.id.icon); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.text.setText(DATA[position]); holder.icon.setImageBitmap(Icons[position]); return convertView;}public static class ViewHolder{ TextView text; ImageView icon;}

第二個參數就是我們最熟悉的convertView呀,難怪平時我們在寫getView()方法是要判斷一下convertView是不是等于null,如果等于null才調用inflate()方法來加載布局,不等于null就可以直接利用convertView,因為convertView就是我們之間利用過的View,只不過被移出屏幕后進入到了廢棄緩存中,現在又重新拿出來使用而已。然后我們只需要把convertView中的數據更新成當前位置上應該顯示的數據,那么看起來就好像是全新加載出來的一個布局一樣,這背后的道理你是不是已經完全搞明白了?

6.9 ListView.setupChild()

/** * Add a view as a child and make sure it is measured (if necessary) and * positioned properly. * * @param child The view to add * @param position The position of this child * @param y The y position relative to which this view will be positioned * @param flowDown If true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @param recycled Has this view been pulled from the recycle bin? If so it * does not need to be remeasured.第二次加載時,recycled = true */ private void setupChild(View child, int position, int y, boolean flowDown, int childrenLeft, boolean selected, boolean recycled) { ... if ((recycled && !p.forceAdd) || (p.recycledHeaderFooter && p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER)) { // 第二次加載時調用 attachViewToParent(child, flowDown ? -1 : 0, p); } else { p.forceAdd = false; if (p.viewType == AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { p.recycledHeaderFooter = true; } // 第一次加載時調用 addViewInLayout(child, flowDown ? -1 : 0, p, true); } ... }

setupChild() 調用 ViewGroup.attachViewToParent() 加載子view 這里寫圖片描述

7. RecycleBin的調用

最后再來看看 RecyleBin 是怎么用的,上邊涉及到RecyleBin的操作如下圖 這里寫圖片描述

下邊介紹下 ListView、AbsListView中是怎么使用RecyleBin的

7.1 ListView調用RecyleBin

7.1.1 ListView.layoutChildren()

@Override protected void layoutChildren() { ... // Pull all children into the RecycleBin. // These views will be reused if possible final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { // 數據沒有變化時,走這里 緩存子view,第一次加載時 ListView中沒有子view,該行不起作用 // 第二次加載時,ListView中已經有子view了,會緩存到 RecyleBin 的 mActiveViews 數組中 recycleBin.fillActiveViews(childCount, firstPosition); } // Clear out old views detachAllViewsFromParent(); // 將所有ListView中的子View清除掉,保證第二次layout不會產生重復數據,從 RecyleBin中取緩存的子View,不會重新inflate recycleBin.removeSkippedScrap(); // 默認的布局模式是 LAYOUT_NORMAL, 走 default switch (mLayoutMode) { ... } // Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); }判斷 dataChanged,如果數據發生變化,就將所有view加入到mScrapView中(recycleBin.addScrapView());否則,將所有view放到mActiveView中(fillActiveViews());recycleBin.removeSkippedScrap();//移除所有old views,根據mLayoutMode,給ListView添加子viewrecycleBin.scrapActiveViews();//刷新緩存,將當前的ActiveVies 移動到 ScrapViews。

7.1.2 ListView.makeAndAddView()

如果數據沒變,調 mRecycler.getActiveView() 獲取一個active view

/** * Obtain the view and add it to our list of children. The view can be made * fresh, converted from an unused view, or used as is if it was in the * recycle bin. * * @param position Logical position in the list * @param y Top or bottom edge of the view to add * @param flow If flow is true, align top edge to y. If false, align bottom * edge to y. * @param childrenLeft Left edge where children should be positioned * @param selected Is this position selected? * @return View that was added */ private View makeAndAddView(int position, int y, boolean flow, int childrenLeft, boolean selected) { View child; if (!mDataChanged) { // Try to use an existing view for this position child = mRecycler.getActiveView(position);// 從 RecyleBin 中獲取一個active view // 第一次進入時 ,child 是null // 第二次進入時,getActiveView()會返回一個View,child != null // 根據RecycleBin的機制,mActiveViews是不能夠重復利用的, 第二次加載時 獲取過,再調用 getActiveView 返回的是 null if (child != null) { // Found it -- we're using an existing child // This just needs to be positioned setupChild(child, position, y, flow, childrenLeft, selected, true); return child; } } // Make a new view for this position, or convert an unused view if possible child = obtainView(position, mIsScrap); // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]); return child; }

7.1.3 ListView.setAdapter()

/** * Sets the data behind this ListView. * * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter}, * depending on the ListView features currently in use. For instance, adding * headers and/or footers will cause the adapter to be wrapped. * * @param adapter The ListAdapter which is responsible for maintaining the * data backing this list and for producing a view to represent an * item in that data set. * * @see #getAdapter() */ @Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null && mDataSetObserver != null) { // 移除了與當前listview的adapter綁定數據集觀察者DataSetObserver mAdapter.unregisterDataSetObserver(mDataSetObserver); } // 重置listview,主要是清除所有的view,改變header、footer的狀態 resetList(); mRecycler.clear();// 清除掉RecycleBin對象mRecycler中所有緩存的view // 判斷是否有headerview和footview if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); } else { mAdapter = adapter; } mOldSelectedPosition = INVALID_POSITION; mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states. super.setAdapter(adapter); if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver();// 注冊headerview的觀察者 mAdapter.registerDataSetObserver(mDataSetObserver); // 在RecycleBin對象mRecycler記錄下item類型的數量 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position; if (mStackFromBottom) { position = lookForSelectablePosition(mItemCount - 1, false); } else { position = lookForSelectablePosition(0, true); } setSelectedPositionInt(position); setNextSelectedPositionInt(position); if (mItemCount == 0) { // Nothing selected checkSelectionChanged(); } } else { mAreAllItemsSelectable = true; checkFocus(); // Nothing selected checkSelectionChanged(); } requestLayout(); }

7.1.4 scrollListItemsBy()

Scroll the children by amount, adding a view at the end and removing views that fall off as necessary. 對子view滑動一定距離,添加view到底部或者移除頂部的不可見view。從注釋看,不可見的item 的自動移除是在scrollListItemsBy中進行的。

Android中view回收的計算是其父view中不再顯示的,如果scrollview中包含了一個wrap_content屬性的listview,里面的內容并不會有任何回收,引起listview 的getheight函數獲取的是一個足以顯示所有內容的高度。

7.2 AbsListView調用RecyleBin

7.2.1 AbsListView.obtainView()

當這個方法被調用時,說明Recycle bin中的view已經不可用了,那么,現在唯一的方法就是,convert一個老的view,或者構造一個新的view

/** * Get a view and have it show the data associated with the specified * position. This is called when we have already discovered that the view is * not available for reuse in the recycle bin. The only choices left are * converting an old view or making a new one. * * @param position The position to display * @param isScrap Array of at least 1 boolean, the first entry will become true if * the returned view was taken from the "temporary detached" scrap heap, false if * otherwise. * * @return A view displaying the data associated with the specified position */ View obtainView(int position, boolean[] isScrap) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "obtainView"); isScrap[0] = false; // Check whether we have a transient state view. Attempt to re-bind the // data and discard the view if we fail. final View transientView = mRecycler.getTransientStateView(position); if (transientView != null) { final LayoutParams params = (LayoutParams) transientView.getLayoutParams(); // If the view type hasn't changed, attempt to re-bind the data. if (params.viewType == mAdapter.getItemViewType(position)) { final View updatedView = mAdapter.getView(position, transientView, this); // If we failed to re-bind the data, scrap the obtained view. if (updatedView != transientView) { setItemViewLayoutParams(updatedView, position); mRecycler.addScrapView(updatedView, position); } } isScrap[0] = true; // Finish the temporary detach started in addScrapView(). transientView.dispatchFinishTemporaryDetach(); return transientView; } // trackMotionScroll()中,一旦有子view移除屏幕,就加入到廢棄緩存中 final View scrapView = mRecycler.getScrapView(position); // 嘗試獲取一個廢棄緩存中的View final View child = mAdapter.getView(position, scrapView, this); // 通過 Adapter.getView() 獲取一個 View // 第一次加載時,scrapView = null if (scrapView != null) { if (child != scrapView) { // 如果重用的scrapView和adapter獲得的view是不一樣的,將scrapView進行回收 // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position);// 將 scrapView 回收 } else { // 如果重用的view和adapter獲得的view是一樣的,將isScrap[0]值為true,否則默認為false if (child.isTemporarilyDetached()) { isScrap[0] = true; // Finish the temporary detach started in addScrapView(). child.dispatchFinishTemporaryDetach(); } else { // we set isScrap to "true" only if the view is temporarily detached. // if the view is fully detached, it is as good as a view created by the // adapter isScrap[0] = false; } } } if (mCacheColorHint != 0) { child.setDrawingCacheBackgroundColor(mCacheColorHint); } if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } setItemViewLayoutParams(child, position); if (AccessibilityManager.getInstance(mContext).isEnabled()) { if (mAccessibilityDelegate == null) { mAccessibilityDelegate = new ListItemAccessibilityDelegate(); } if (child.getAccessibilityDelegate() == null) { child.setAccessibilityDelegate(mAccessibilityDelegate); } } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return child; }

7.2.2 trackMotionScroll()

/** * Track a motion scroll * * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * deltaY 表示 從手指按下時的位置 到 當前手指位置的距離 * @param incrementalDeltaY Change in deltaY from the previous event. * incrementalDeltaY 表示據上次觸發event事件 手指在 y方向上的該變量,通過正負值判斷向上或向下滑動 * @return true if we're already at the beginning/end of the list and have nothing to do. */ boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { ... // 滾動時,不在可見范圍內的item放入回收站 if (down) { // 手指上滑 int top = -incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { top += listPadding.top; } for (int i = 0; i < childCount; i++) { // 循環 從上往下依次獲取子view final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { // 子view已經移出屏幕了 count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position);// 將該view加入到廢棄緩存中 } } } } else { // 手指下滑 int bottom = getHeight() - incrementalDeltaY; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { bottom -= listPadding.bottom; } for (int i = childCount - 1; i >= 0; i--) { // 從下往上 依次獲取子view final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { // 子view已經移出屏幕了 start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. child.clearAccessibilityFocus(); mRecycler.addScrapView(child, position); } } } } mMotionViewNewTop = mMotionViewOriginalTop + deltaY; mBlockLayoutRequests = true; if (count > 0) { detachViewsFromParent(start, count);// 把移出屏幕的子view全detach掉,看不到的就不保存了 mRecycler.removeSkippedScrap(); } // invalidate before moving the children to avoid unnecessary invalidate // calls to bubble up from the children all the way to the top if (!awakenScrollBars()) { invalidate(); } offsetChildrenTopAndBottom(incrementalDeltaY);// 所有的子View按照 incrementalDeltaY 進行偏移,實現了隨著手指拖動,ListView隨著滾動的效果 if (down) { mFirstPosition += count; } final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { // ListView的最后一個子View的底部已經移入屏幕 或者 第一個子View的頂部已經移入屏幕 fillGap(down); } mRecycler.fullyDetachScrapViews(); ... }
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 嘉鱼县| 六盘水市| 黑河市| 安顺市| 上高县| 桂东县| 清苑县| 囊谦县| 洛宁县| 广州市| 康保县| 洛南县| 南丹县| 稻城县| 娄底市| 砚山县| 奈曼旗| 万源市| 团风县| 苗栗市| 望都县| 通州市| 金堂县| 弋阳县| 潜山县| 尼勒克县| 江口县| 凌云县| 陇西县| 南靖县| 桂东县| 惠东县| 周口市| 资兴市| 巴彦淖尔市| 托里县| 漠河县| 获嘉县| 北碚区| 阿瓦提县| 宣武区|