}
/**
- * Put a view into the ScrapViews list. These views are unordered.
+ * 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) {
- AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
+ final AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
if (lp == null) {
return;
}
lp.scrappedFromPosition = position;
- // Don't put header or footer views or views that should be ignored
- // into the scrap heap
- int viewType = lp.viewType;
+ // Don't scrap header or footer views, or views that should
+ // otherwise not be recycled.
+ final int viewType = lp.viewType;
+ if (!shouldRecycleViewType(viewType)) {
+ return;
+ }
+
+ scrap.dispatchStartTemporaryDetach();
+
+ // Don't scrap views that have transient state.
final boolean scrapHasTransientState = scrap.hasTransientState();
- if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
- if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && scrapHasTransientState) {
+ 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<View>();
+ }
+ 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<View>();
+ }
+ mTransientStateViews.put(position, scrap);
+ } else {
+ // Otherwise, we'll have to remove the view and start over.
if (mSkippedScrap == null) {
mSkippedScrap = new ArrayList<View>();
}
mSkippedScrap.add(scrap);
}
- if (scrapHasTransientState) {
- scrap.dispatchStartTemporaryDetach();
- if (mAdapter != null && mAdapterHasStableIds) {
- if (mTransientStateViewsById == null) {
- mTransientStateViewsById = new LongSparseArray<View>();
- }
- mTransientStateViewsById.put(lp.itemId, scrap);
- } else if (!mDataChanged) {
- // avoid putting views on transient state list during a data change;
- // the layout positions may be out of sync with the adapter positions
- if (mTransientStateViews == null) {
- mTransientStateViews = new SparseArray<View>();
- }
- mTransientStateViews.put(position, scrap);
- }
+ } else {
+ if (mViewTypeCount == 1) {
+ mCurrentScrap.add(scrap);
+ } else {
+ mScrapViews[viewType].add(scrap);
}
- return;
- }
- scrap.dispatchStartTemporaryDetach();
- if (mViewTypeCount == 1) {
- mCurrentScrap.add(scrap);
- } else {
- mScrapViews[viewType].add(scrap);
- }
+ scrap.setAccessibilityDelegate(null);
- scrap.setAccessibilityDelegate(null);
- if (mRecyclerListener != null) {
- mRecyclerListener.onMovedToScrapHeap(scrap);
+ if (mRecyclerListener != null) {
+ mRecyclerListener.onMovedToScrapHeap(scrap);
+ }
}
}
@Override
protected void layoutChildren() {
final boolean blockLayoutRequests = mBlockLayoutRequests;
- if (!blockLayoutRequests) {
- mBlockLayoutRequests = true;
- } else {
+ if (blockLayoutRequests) {
return;
}
+ mBlockLayoutRequests = true;
+
try {
super.layoutChildren();
return;
}
- int childrenTop = mListPadding.top;
- int childrenBottom = mBottom - mTop - mListPadding.bottom;
+ final int childrenTop = mListPadding.top;
+ final int childrenBottom = mBottom - mTop - mListPadding.bottom;
+ final int childCount = getChildCount();
- int childCount = getChildCount();
int index = 0;
int delta = 0;
View oldFirst = null;
View newSel = null;
- View focusLayoutRestoreView = null;
-
AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
View accessibilityFocusLayoutRestoreView = null;
int accessibilityFocusPosition = INVALID_POSITION;
// Remember which child, if any, had accessibility focus. This must
// occur before recycling any views, since that will clear
// accessibility focus.
+ // TODO: This should rely on transient state.
final ViewRootImpl viewRootImpl = getViewRootImpl();
if (viewRootImpl != null) {
final View accessFocusedView = viewRootImpl.getAccessibilityFocusedHost();
}
}
+ // Ensure the child containing focus, if any, has transient state.
+ // If the list data hasn't changed, or if the adapter has stable
+ // IDs, this will maintain focus.
+ final View focusedChild = getFocusedChild();
+ if (focusedChild != null) {
+ focusedChild.setHasTransientState(true);
+ }
+
// Pull all children into the RecycleBin.
// These views will be reused if possible
final int firstPosition = mFirstPosition;
final RecycleBin recycleBin = mRecycler;
-
- // reset the focus restoration
- View focusLayoutRestoreDirectChild = null;
-
- // Don't put header or footer views into the Recycler. Those are
- // already cached in mHeaderViews;
if (dataChanged) {
for (int i = 0; i < childCount; i++) {
recycleBin.addScrapView(getChildAt(i), firstPosition+i);
recycleBin.fillActiveViews(childCount, firstPosition);
}
- // take focus back to us temporarily to avoid the eventual
- // call to clear focus when removing the focused child below
- // from messing things up when ViewAncestor assigns focus back
- // to someone else
- final View focusedChild = getFocusedChild();
- if (focusedChild != null) {
- // TODO: in some cases focusedChild.getParent() == null
-
- // we can remember the focused view to restore after relayout if the
- // data hasn't changed, or if the focused position is a header or footer
- if (!dataChanged || isDirectChildHeaderOrFooter(focusedChild)) {
- focusLayoutRestoreDirectChild = focusedChild;
- // remember the specific view that had focus
- focusLayoutRestoreView = findFocus();
- if (focusLayoutRestoreView != null) {
- // tell it we are going to mess with it
- focusLayoutRestoreView.onStartTemporaryDetach();
- }
- }
- requestFocus();
- }
-
// Clear out old views
detachAllViewsFromParent();
recycleBin.removeSkippedScrap();
recycleBin.scrapActiveViews();
if (sel != null) {
- // the current selected item should get focus if items
- // are focusable
- if (mItemsCanFocus && hasFocus() && !sel.hasFocus()) {
- final boolean focusWasTaken = (sel == focusLayoutRestoreDirectChild &&
- focusLayoutRestoreView != null &&
- focusLayoutRestoreView.requestFocus()) || sel.requestFocus();
- if (!focusWasTaken) {
- // selected item didn't take focus, fine, but still want
- // to make sure something else outside of the selected view
- // has focus
+ final boolean shouldPlaceFocus = mItemsCanFocus && hasFocus();
+ final boolean maintainedFocus = focusedChild != null && focusedChild.hasFocus();
+ if (shouldPlaceFocus && !maintainedFocus && !sel.hasFocus()) {
+ if (sel.requestFocus()) {
+ // Successfully placed focus, clear selection.
+ sel.setSelected(false);
+ mSelectorRect.setEmpty();
+ } else {
+ // Failed to place focus, clear current (invalid) focus.
final View focused = getFocusedChild();
if (focused != null) {
focused.clearFocus();
}
positionSelector(INVALID_POSITION, sel);
- } else {
- sel.setSelected(false);
- mSelectorRect.setEmpty();
}
} else {
positionSelector(INVALID_POSITION, sel);
}
mSelectedTop = sel.getTop();
} else {
- if (mTouchMode > TOUCH_MODE_DOWN && mTouchMode < TOUCH_MODE_SCROLL) {
- View child = getChildAt(mMotionPosition - mFirstPosition);
- if (child != null) positionSelector(mMotionPosition, child);
+ // If the user's finger is down, select the motion position.
+ // Otherwise, clear selection.
+ if (mTouchMode == TOUCH_MODE_TAP || mTouchMode == TOUCH_MODE_DONE_WAITING) {
+ final View child = getChildAt(mMotionPosition - mFirstPosition);
+ if (child != null) {
+ positionSelector(mMotionPosition, child);
+ }
} else {
mSelectedTop = 0;
mSelectorRect.setEmpty();
}
-
- // even if there is not selected position, we may need to restore
- // focus (i.e. something focusable in touch mode)
- if (hasFocus() && focusLayoutRestoreView != null) {
- focusLayoutRestoreView.requestFocus();
- }
}
// Attempt to restore accessibility focus.
}
}
- // tell focus view we are done mucking with it, if it is still in
- // our view hierarchy.
- if (focusLayoutRestoreView != null
- && focusLayoutRestoreView.getWindowToken() != null) {
- focusLayoutRestoreView.onFinishTemporaryDetach();
+ if (focusedChild != null) {
+ focusedChild.setHasTransientState(false);
}
mLayoutMode = LAYOUT_NORMAL;