//..... /** * 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 = newView[0];
/** * Unsorted views that can be used by the adapter as a convert view. */ private ArrayList<View>[] mScrapViews; //..... /** * 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 */ voidfillActiveViews(int childCount, int firstActivePosition) { if (mActiveViews.length < childCount) { mActiveViews = newView[childCount]; } mFirstActivePosition = firstActivePosition;
//noinspection MismatchedReadAndWriteOfArray final View[] activeViews = mActiveViews; for (inti=0; i < childCount; i++) { Viewchild= getChildAt(i); AbsListView.LayoutParamslp= (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; } } }
/** * 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) { intindex= position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { finalViewmatch= activeViews[index]; activeViews[index] = null; return match; } returnnull; }
/** * @return A view from the ScrapViews collection. These are unordered. */ View getScrapView(int position) { finalintwhichScrap= mAdapter.getItemViewType(position); if (whichScrap < 0) { returnnull; } if (mViewTypeCount == 1) { return retrieveFromScrap(mCurrentScrap, position); } elseif (whichScrap < mScrapViews.length) { return retrieveFromScrap(mScrapViews[whichScrap], position); } returnnull; }
/** * 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 */ voidaddScrapView(View scrap, int position) { final AbsListView.LayoutParamslp= (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. finalintviewType= 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. finalbooleanscrapHasTransientState= 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 = newLongSparseArray<>(); } mTransientStateViewsById.put(lp.itemId, scrap); } elseif (!mDataChanged) { // If the data hasn't changed, we can reuse the views at // their old positions. if (mTransientStateViews == null) { mTransientStateViews = newSparseArray<>(); } 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) { mRecyclerListener.onMovedToScrapHeap(scrap); } } } }
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.
// Flush any cached views that did not get reused above recycleBin.scrapActiveViews(); //...... } finally { //...... } }
在介绍之前我们先看下如下代码片:
final int firstPosition = mFirstPosition; final RecycleBin recycleBin = mRecycler; if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i), firstPosition+i); } } else { recycleBin.fillActiveViews(childCount, firstPosition); }
if (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); } }
/** * Fills the list from top to bottom, starting with mFirstPosition * * @param nextTop The location where the top of the first item should be * drawn * * @return The view that is currently selected */ private View fillFromTop(intnextTop) { mFirstPosition = Math.min(mFirstPosition, mSelectedPosition); mFirstPosition = Math.min(mFirstPosition, mItemCount - 1); if (mFirstPosition < 0) { mFirstPosition = 0; } return fillDown(mFirstPosition, nextTop); }
片段没实在的作用,实际的功能位于fillDown中。
private View fillDown(intpos, intnextTop) { View selectedView = null; intend = (mBottom - mTop); if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { end -= mListPadding.bottom; } 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++; } setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1); return selectedView; }
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); 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; }
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { return true; }
final int firstTop = getChildAt(0).getTop(); final int lastBottom = getChildAt(childCount - 1).getBottom();
final Rect listPadding = mListPadding;
// "effective padding" In this case is the amount of padding that affects // how much space should not be filled by items. If we don't clip to padding // there is no effective padding. int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) { effectivePaddingTop = listPadding.top; effectivePaddingBottom = listPadding.bottom; }
// FIXME account for grid vertical spacing too? final int spaceAbove = effectivePaddingTop - firstTop; final int end = getHeight() - effectivePaddingBottom; final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop; if (deltaY < 0) { deltaY = Math.max(-(height - 1), deltaY); } else { deltaY = Math.min(height - 1, deltaY); }
if (cannotScrollDown || cannotScrollUp) { return incrementalDeltaY != 0; }
final boolean down = incrementalDeltaY < 0;
final boolean inTouchMode = isInTouchMode(); if (inTouchMode) { hideSelector(); }
final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0; int count = 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++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { 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); } } } } 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--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { 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); 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);
if (down) { mFirstPosition += count; }
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(down); }
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) { final int childIndex = mSelectedPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(mSelectedPosition, getChildAt(childIndex)); } } else if (mSelectorPosition != INVALID_POSITION) { final int childIndex = mSelectorPosition - mFirstPosition; if (childIndex >= 0 && childIndex < getChildCount()) { positionSelector(INVALID_POSITION, getChildAt(childIndex)); } } else { mSelectorRect.setEmpty(); } mBlockLayoutRequests = false; invokeOnItemScrollListener(); return false; }
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); 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; }
为什么是null我们再来看下getActiveView:
View getActiveView(int position) { intindex = position - mFirstActivePosition; final View[] activeViews = mActiveViews; if (index >=0 && index < activeViews.length) { final View match = activeViews[index]; activeViews[index] = null; return match; } returnnull; }