編輯:關於Android編程
最近用到RecyclerView,想研究RecyclerView和ListView復用機制的區別,這篇文章以解析源碼的方式解析ListView復用機制。
/** * 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. */RecycleBin是2級的存儲結構,
ActiveViews: 當前屏幕上的活動View
ScrapViews: 廢棄View,可復用的舊View
//回收Listener,當View變為可回收,即ScrapView時,會通過mRecyclerListener通知注冊者,listener可通過setRecyclerListener注冊 private RecyclerListener mRecyclerListener; /** * The position of the first view stored in mActiveViews. */ // 第一個活動view的position,即第一個可視view的position 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. */ // 活動view的集合 private View[] mActiveViews = new View[0]; /** * Unsorted views that can be used by the adapter as a convert view. */ /**廢棄的可修復view集合,復用時傳遞到Adapter#getView方法的convertView參數。 * 因為item type可能大於1,只有view type相同的view之間才能復用,所以是個二維數組 */ private ArrayList[] mScrapViews; // ListView item type數量 private int mViewTypeCount; // 當前的廢棄view數組,定義這個成員是為了在mViewTypeCount為1時使用方便,不需要去取mScrapViews的第一個元素 private ArrayList mCurrentScrap; // 被跳過的,不能復用的view集合。view type小於0或者處理transient狀態的view不能被復用。 private ArrayList mSkippedScrap; // 處於transient狀態的view集合,處於transient狀態的view不能被復用,如view的動畫正在播放, // transient是瞬時、過渡的意思,關於transient狀態詳見android.view.View#PFLAG2_HAS_TRANSIENT_STATE private SparseArray mTransientStateViews; // 如果adapter的hasStableIds方法返回true,處於過度狀態的view保存到這裡。因為需要保存view的position,而且處於過度狀態的view一般很少, // 這2個成員用了稀疏數組。具體不需要case,知道是保存轉換狀態view的集合就行。 private LongSparseArray mTransientStateViewsById;
從RecycleBin成員變量的定義基本可以看出復用的原理:
1. 廢棄的view保存在一個數組中,復用時從中取出
2. 擁有相同view type的view之間才能復用,所以mScrapViews是個二維數組
3. 處於transient狀態的view不能被復用
public void setViewTypeCount(int viewTypeCount) { if (viewTypeCount < 1) { throw new IllegalArgumentException("Can't have a viewTypeCount < 1"); } //noinspectionmarkChildrenDirty: 當ListView size或position變化時,設置mScrapViews和transient views的forceLayout flag,在下一次被復用時會重新布置。[] scrapViews = new ArrayList[viewTypeCount]; for (int i = 0; i < viewTypeCount; i++) { scrapViews[i] = new ArrayList (); } mViewTypeCount = viewTypeCount; mCurrentScrap = scrapViews[0]; mScrapViews = scrapViews; }
public void markChildrenDirty() { if (mViewTypeCount == 1) { final ArrayListscrap = 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 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(); } } }
public boolean shouldRecycleViewType(int viewType) { return viewType >= 0; }
/** * 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; } } }getActiveView: 查找mActiveView中指定position的view,找到後將從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; return match; } return null; }getScrapView: 查找復用的view,先通過position從adapter中找到view type,然後再從相應的scrap中查找
/** * @return A view from the ScrapViews collection. These are unordered. */ View getScrapView(int position) { final int whichScrap = mAdapter.getItemViewType(position); if (whichScrap < 0) { return null; } if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); } else if (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } return null; }
private View retrieveFromScrap(ArrayListscrapViews, 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) { final View scrap = scrapViews.remove(i); clearAccessibilityFromScrap(scrap); return scrap; } } final View scrap = scrapViews.remove(size - 1); clearAccessibilityFromScrap(scrap); return scrap; } else { return null; } }
/** * Puts a view into the list of scrap views. * * 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) { 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; } 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)) { //不能被復用的view type,並且不為header或footer時加入skipped scrap // 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) { //view處於transient狀態 if (mAdapter != null && mAdapterHasStableIds) { //adpater has stable ids,加入mTransientStateViewsById中 // 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) { //數據未變化,加入mTransientStateViews中 // 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 { //adapter為null或adpater不為null,無stable ids,且數據變化,則丟棄scrap view // Otherwise, we'll have to remove the view and start over. getSkippedScrap().add(scrap); } } else { //不處於transient狀態,將scrap view加入mScarpViews中 if (mViewTypeCount == 1) { //view type count為1時,為方便,直接操作mCurrentScrap mCurrentScrap.add(scrap); } else { mScrapViews[viewType].add(scrap); } if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }
/** * Move all views remaining in mActiveViews to mScrapViews. */ void scrapActiveViews() { final View[] activeViews = mActiveViews; final boolean hasListener = mRecyclerListener != null; final boolean multipleScraps = mViewTypeCount > 1; ArrayListscrapViews = 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 (); } long id = mAdapter.getItemId(mFirstActivePosition + i); mTransientStateViewsById.put(id, victim); } else if (!mDataChanged) { if (mTransientStateViews == null) { mTransientStateViews = new SparseArray (); } mTransientStateViews.put(mFirstActivePosition + i, victim); } else if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { // The data has changed, we can't keep this view. removeDetachedView(victim, false); } } else if (!shouldRecycleViewType(whichScrap)) { // Discard non-recyclable views except headers/footers. if (whichScrap != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) { removeDetachedView(victim, false); } } else { // Store everything else on the appropriate scrap heap. if (multipleScraps) { scrapViews = mScrapViews[whichScrap]; } victim.dispatchStartTemporaryDetach(); lp.scrappedFromPosition = mFirstActivePosition + i; scrapViews.add(victim); if (hasListener) { mRecyclerListener.onMovedToScrapHeap(victim); } } } } pruneScrapViews(); }
//清除所有scrap view和transient view void clear(); //獲取當前position下的transient view,如無則返回null View getTransientStateView(int position); //獲取mSkippedScrap private ArrayListgetSkippedScrap(); //清除mSkippedScrap void removeSkippedScrap(); //裁剪mScarpViews,確保size不大於mActiveViews的size;移除已不是transient state的view private void pruneScrapViews(); //將所有scrap views放入指定的views中,這個方法沒弄明白,不知何時調用 void reclaimScrapViews(List views); //為每一個scrap和active view設置緩存背影色 void setCacheColorHint(int color); //從ListView層次中移除view private void removeDetachedView(View child, boolean animate);
首先來看一下效果圖; 先說一下我的需求:查看群成員,如果超過15人則全部顯示,如果大於15人則先加載15人,其余的不顯示,點擊查看更多則加載全部。再來說一下我
TextSwitcher的Java Doc是這樣描述自己的: Specialized ViewSwitcher that contains only children o
使用AS從代碼托管中心下載項目(我使用的是Coding托管代碼)1、打開Android Studio,如果之前沒有打開過任何項目,那麼將會看到下面的啟動頁面,並選擇Che
當我們調試安卓機器時,第一次插上usb線,會彈出一個授權的對話框,(前提是打開了usb調試功能)點擊確認,才會允許調試.如果我們想機器默認就可以調試該怎麼做呢?如果我們想