true
so that views will be avilable to other RecyclerViews
- * immediately.
- *
- * Note that, setting this flag will result in a performance drop if RecyclerView
- * is restored.
- *
- * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
- */
- public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
- mRecycleChildrenOnDetach = recycleChildrenOnDetach;
- }
-
- @Override
- public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
- super.onDetachedFromWindow(view, recycler);
- if (mRecycleChildrenOnDetach) {
- removeAndRecycleAllViews(recycler);
- recycler.clear();
- }
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- if (getChildCount() > 0) {
- final AccessibilityRecordCompat record = AccessibilityEventCompat
- .asRecord(event);
- record.setFromIndex(findFirstVisibleItemPosition());
- record.setToIndex(findLastVisibleItemPosition());
- }
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- if (mPendingSavedState != null) {
- return new SavedState(mPendingSavedState);
- }
- SavedState state = new SavedState();
- if (getChildCount() > 0) {
- ensureLayoutState();
- boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
- state.mAnchorLayoutFromEnd = didLayoutFromEnd;
- if (didLayoutFromEnd) {
- final View refChild = getChildClosestToEnd();
- state.mAnchorOffset = mOrientationHelper.getEndAfterPadding() -
- mOrientationHelper.getDecoratedEnd(refChild);
- state.mAnchorPosition = getPosition(refChild);
- } else {
- final View refChild = getChildClosestToStart();
- state.mAnchorPosition = getPosition(refChild);
- state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild) -
- mOrientationHelper.getStartAfterPadding();
- }
- } else {
- state.invalidateAnchor();
- }
- return state;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- if (state instanceof SavedState) {
- mPendingSavedState = (SavedState) state;
- requestLayout();
- if (DEBUG) {
- Log.d(TAG, "loaded saved state");
- }
- } else if (DEBUG) {
- Log.d(TAG, "invalid saved state class");
- }
- }
-
- /**
- * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
- */
- @Override
- public boolean canScrollHorizontally() {
- return mOrientation == HORIZONTAL;
- }
-
- /**
- * @return true if {@link #getOrientation()} is {@link #VERTICAL}
- */
- @Override
- public boolean canScrollVertically() {
- return mOrientation == VERTICAL;
- }
-
- /**
- * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
- */
- public void setStackFromEnd(boolean stackFromEnd) {
- assertNotInLayoutOrScroll(null);
- if (mStackFromEnd == stackFromEnd) {
- return;
- }
- mStackFromEnd = stackFromEnd;
- requestLayout();
- }
-
- public boolean getStackFromEnd() {
- return mStackFromEnd;
- }
-
- /**
- * Returns the current orientaion of the layout.
- *
- * @return Current orientation.
- * @see #mOrientation
- * @see #setOrientation(int)
- */
- public int getOrientation() {
- return mOrientation;
- }
-
- /**
- * Sets the orientation of the layout. {@link android.support.v7.widget.LinearLayoutManager}
- * will do its best to keep scroll position.
- *
- * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
- */
- public void setOrientation(int orientation) {
- if (orientation != HORIZONTAL && orientation != VERTICAL) {
- throw new IllegalArgumentException("invalid orientation:" + orientation);
- }
- assertNotInLayoutOrScroll(null);
- if (orientation == mOrientation) {
- return;
- }
- mOrientation = orientation;
- mOrientationHelper = null;
- requestLayout();
- }
-
- /**
- * Calculates the view layout order. (e.g. from end to start or start to end)
- * RTL layout support is applied automatically. So if layout is RTL and
- * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
- */
- private void resolveShouldLayoutReverse() {
- // A == B is the same result, but we rather keep it readable
- if (mOrientation == VERTICAL || !isLayoutRTL()) {
- mShouldReverseLayout = mReverseLayout;
- } else {
- mShouldReverseLayout = !mReverseLayout;
- }
- }
-
- /**
- * Returns if views are laid out from the opposite direction of the layout.
- *
- * @return If layout is reversed or not.
- * @see {@link #setReverseLayout(boolean)}
- */
- public boolean getReverseLayout() {
- return mReverseLayout;
- }
-
- /**
- * Used to reverse item traversal and layout order.
- * This behaves similar to the layout change for RTL views. When set to true, first item is
- * laid out at the end of the UI, second item is laid out before it etc.
- *
- * For horizontal layouts, it depends on the layout direction.
- * When set to true, If {@link android.support.v7.widget.RecyclerView} is LTR, than it will
- * layout from RTL, if {@link android.support.v7.widget.RecyclerView}} is RTL, it will layout
- * from LTR.
- *
- * If you are looking for the exact same behavior of
- * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
- * {@link #setStackFromEnd(boolean)}
- */
- public void setReverseLayout(boolean reverseLayout) {
- assertNotInLayoutOrScroll(null);
- if (reverseLayout == mReverseLayout) {
- return;
- }
- mReverseLayout = reverseLayout;
- requestLayout();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public View findViewByPosition(int position) {
- final int childCount = getChildCount();
- if (childCount == 0) {
- return null;
- }
- final int firstChild = getPosition(getChildAt(0));
- final int viewPosition = position - firstChild;
- if (viewPosition >= 0 && viewPosition < childCount) {
- return getChildAt(viewPosition);
- }
- return null;
- }
-
- /**
- * Returns the amount of extra space that should be laid out by LayoutManager. - * By default, {@link android.support.v7.widget.LinearLayoutManager} lays out 1 extra page of - * items while smooth scrolling and 0 otherwise. You can override this method to implement your - * custom layout pre-cache logic.
- *Laying out invisible elements will eventually come with performance cost. On the other - * hand, in places like smooth scrolling to an unknown location, this extra content helps - * LayoutManager to calculate a much smoother scrolling; which improves user experience.
- *You can also use this if you are trying to pre-layout your upcoming views.
- * - * @return The extra space that should be laid out (in pixels). - */ - protected int getExtraLayoutSpace(RecyclerView.State state) { - if (state.hasTargetScrollPosition()) { - return mOrientationHelper.getTotalSpace(); - } else { - return 0; - } - } - - @Override - public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, - int position) { - LinearSmoothScroller linearSmoothScroller = - new LinearSmoothScroller(recyclerView.getContext()) { - @Override - public PointF computeScrollVectorForPosition(int targetPosition) { - return LinearLayoutManager.this - .computeScrollVectorForPosition(targetPosition); - } - }; - linearSmoothScroller.setTargetPosition(position); - startSmoothScroll(linearSmoothScroller); - } - - public PointF computeScrollVectorForPosition(int targetPosition) { - if (getChildCount() == 0) { - return null; - } - final int firstChildPos = getPosition(getChildAt(0)); - final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1; - if (mOrientation == HORIZONTAL) { - return new PointF(direction, 0); - } else { - return new PointF(0, direction); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { - // layout algorithm: - // 1) by checking children and other variables, find an anchor coordinate and an anchor - // item position. - // 2) fill towards start, stacking from bottom - // 3) fill towards end, stacking from top - // 4) scroll to fulfill requirements like stack from bottom. - // create layout state - if (DEBUG) { - Log.d(TAG, "is pre layout:" + state.isPreLayout()); - } - if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) { - mPendingScrollPosition = mPendingSavedState.mAnchorPosition; - } - - ensureLayoutState(); - mLayoutState.mRecycle = false; - // resolve layout direction - resolveShouldLayoutReverse(); - - mAnchorInfo.reset(); - mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; - // calculate anchor position and coordinate - updateAnchorInfoForLayout(state, mAnchorInfo); - if (DEBUG) { - Log.d(TAG, "Anchor info:" + mAnchorInfo); - } - - // LLM may decide to layout items for "extra" pixels to account for scrolling target, - // caching or predictive animations. - int extraForStart; - int extraForEnd; - final int extra = getExtraLayoutSpace(state); - boolean before = state.getTargetScrollPosition() < mAnchorInfo.mPosition; - if (before == mShouldReverseLayout) { - extraForEnd = extra; - extraForStart = 0; - } else { - extraForStart = extra; - extraForEnd = 0; - } - extraForStart += mOrientationHelper.getStartAfterPadding(); - extraForEnd += mOrientationHelper.getEndPadding(); - if (state.isPreLayout() && mPendingScrollPosition != NO_POSITION && - mPendingScrollPositionOffset != INVALID_OFFSET) { - // if the child is visible and we are going to move it around, we should layout - // extra items in the opposite direction to make sure new items animate nicely - // instead of just fading in - final View existing = findViewByPosition(mPendingScrollPosition); - if (existing != null) { - final int current; - final int upcomingOffset; - if (mShouldReverseLayout) { - current = mOrientationHelper.getEndAfterPadding() - - mOrientationHelper.getDecoratedEnd(existing); - upcomingOffset = current - mPendingScrollPositionOffset; - } else { - current = mOrientationHelper.getDecoratedStart(existing) - - mOrientationHelper.getStartAfterPadding(); - upcomingOffset = mPendingScrollPositionOffset - current; - } - if (upcomingOffset > 0) { - extraForStart += upcomingOffset; - } else { - extraForEnd -= upcomingOffset; - } - } - } - int startOffset; - int endOffset; - onAnchorReady(state, mAnchorInfo); - detachAndScrapAttachedViews(recycler); - mLayoutState.mIsPreLayout = state.isPreLayout(); - if (mAnchorInfo.mLayoutFromEnd) { - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForEnd += mLayoutState.mAvailable; - } - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - } else { - // fill towards end - updateLayoutStateToFillEnd(mAnchorInfo); - mLayoutState.mExtra = extraForEnd; - fill(recycler, mLayoutState, state, false); - endOffset = mLayoutState.mOffset; - if (mLayoutState.mAvailable > 0) { - extraForStart += mLayoutState.mAvailable; - } - // fill towards start - updateLayoutStateToFillStart(mAnchorInfo); - mLayoutState.mExtra = extraForStart; - mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; - fill(recycler, mLayoutState, state, false); - startOffset = mLayoutState.mOffset; - } - - // changes may cause gaps on the UI, try to fix them. - // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have - // changed - if (getChildCount() > 0) { - // because layout from end may be changed by scroll to position - // we re-calculate it. - // find which side we should check for gaps. - if (mShouldReverseLayout ^ mStackFromEnd) { - int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutStartGap(startOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } else { - int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true); - startOffset += fixOffset; - endOffset += fixOffset; - fixOffset = fixLayoutEndGap(endOffset, recycler, state, false); - startOffset += fixOffset; - endOffset += fixOffset; - } - } - layoutForPredictiveAnimations(recycler, state, startOffset, endOffset); - if (!state.isPreLayout()) { - mPendingScrollPosition = NO_POSITION; - mPendingScrollPositionOffset = INVALID_OFFSET; - mOrientationHelper.onLayoutComplete(); - } - mLastStackFromEnd = mStackFromEnd; - mPendingSavedState = null; // we don't need this anymore - if (DEBUG) { - validateChildOrder(); - } - } - - /** - * Method called when Anchor position is decided. Extending class can setup accordingly or - * even update anchor info if necessary. - * - * @param state - * @param anchorInfo Simple data structure to keep anchor point information for the next layout - */ - void onAnchorReady(RecyclerView.State state, AnchorInfo anchorInfo) { - } - - /** - * If necessary, layouts new items for predictive animations - */ - private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler, - RecyclerView.State state, int startOffset, int endOffset) { - // If there are scrap children that we did not layout, we need to find where they did go - // and layout them accordingly so that animations can work as expected. - // This case may happen if new views are added or an existing view expands and pushes - // another view out of bounds. - if (!state.willRunPredictiveAnimations() || getChildCount() == 0 || state.isPreLayout() - || !supportsPredictiveItemAnimations()) { - return; - } - - // to make the logic simpler, we calculate the size of children and call fill. - int scrapExtraStart = 0, scrapExtraEnd = 0; - final ListScroll the RecyclerView to make the position visible.
- * - *RecyclerView will scroll the minimum amount that is necessary to make the - * target position visible. If you are looking for a similar behavior to - * {@link android.widget.ListView#setSelection(int)} or - * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use - * {@link #scrollToPositionWithOffset(int, int)}.
- * - *Note that scroll position change will not be reflected until the next layout call.
- * - * @param position Scroll to this adapter position - * @see #scrollToPositionWithOffset(int, int) - */ - @Override - public void scrollToPosition(int position) { - mPendingScrollPosition = position; - mPendingScrollPositionOffset = INVALID_OFFSET; - if (mPendingSavedState != null) { - mPendingSavedState.invalidateAnchor(); - } - requestLayout(); - } - - /** - * Scroll to the specified adapter position with the given offset from resolved layout - * start. Resolved layout start depends on {@link #getReverseLayout()}, - * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}. - * - * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling - *scrollToPositionWithOffset(10, 20)
will layout such that
- * item[10]
's bottom is 20 pixels above the RecyclerView's bottom.
- *
- * Note that scroll position change will not be reflected until the next layout call.
- *
- *
- * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
- *
- * @param position Index (starting at 0) of the reference item.
- * @param offset The distance (in pixels) between the start edge of the item view and
- * start edge of the RecyclerView.
- * @see #setReverseLayout(boolean)
- * @see #scrollToPosition(int)
- */
- public void scrollToPositionWithOffset(int position, int offset) {
- mPendingScrollPosition = position;
- mPendingScrollPositionOffset = offset;
- if (mPendingSavedState != null) {
- mPendingSavedState.invalidateAnchor();
- }
- requestLayout();
- }
-
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
- RecyclerView.State state) {
- if (mOrientation == VERTICAL) {
- return 0;
- }
- return scrollBy(dx, recycler, state);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
- RecyclerView.State state) {
- if (mOrientation == HORIZONTAL) {
- return 0;
- }
- return scrollBy(dy, recycler, state);
- }
-
- @Override
- public int computeHorizontalScrollOffset(RecyclerView.State state) {
- return computeScrollOffset(state);
- }
-
- @Override
- public int computeVerticalScrollOffset(RecyclerView.State state) {
- return computeScrollOffset(state);
- }
-
- @Override
- public int computeHorizontalScrollExtent(RecyclerView.State state) {
- return computeScrollExtent(state);
- }
-
- @Override
- public int computeVerticalScrollExtent(RecyclerView.State state) {
- return computeScrollExtent(state);
- }
-
- @Override
- public int computeHorizontalScrollRange(RecyclerView.State state) {
- return computeScrollRange(state);
- }
-
- @Override
- public int computeVerticalScrollRange(RecyclerView.State state) {
- return computeScrollRange(state);
- }
-
- private int computeScrollOffset(RecyclerView.State state) {
- if (getChildCount() == 0) {
- return 0;
- }
- ensureLayoutState();
- return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled, mShouldReverseLayout);
- }
-
- private int computeScrollExtent(RecyclerView.State state) {
- if (getChildCount() == 0) {
- return 0;
- }
- ensureLayoutState();
- return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled);
- }
-
- private int computeScrollRange(RecyclerView.State state) {
- if (getChildCount() == 0) {
- return 0;
- }
- ensureLayoutState();
- return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
- getChildClosestToStart(), getChildClosestToEnd(), this,
- mSmoothScrollbarEnabled);
- }
-
- /**
- * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
- * based on the number of visible pixels in the visible items. This however assumes that all
- * list items have similar or equal widths or heights (depending on list orientation).
- * If you use a list in which items have different dimensions, the scrollbar will change
- * appearance as the user scrolls through the list. To avoid this issue, you need to disable
- * this property.
- *
- * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
- * solely on the number of items in the adapter and the position of the visible items inside
- * the adapter. This provides a stable scrollbar as the user navigates through a list of items
- * with varying widths / heights.
- *
- * @param enabled Whether or not to enable smooth scrollbar.
- * @see #setSmoothScrollbarEnabled(boolean)
- */
- public void setSmoothScrollbarEnabled(boolean enabled) {
- mSmoothScrollbarEnabled = enabled;
- }
-
- /**
- * Returns the current state of the smooth scrollbar feature. It is enabled by default.
- *
- * @return True if smooth scrollbar is enabled, false otherwise.
- * @see #setSmoothScrollbarEnabled(boolean)
- */
- public boolean isSmoothScrollbarEnabled() {
- return mSmoothScrollbarEnabled;
- }
-
- private void updateLayoutState(int layoutDirection, int requiredSpace,
- boolean canUseExistingSpace, RecyclerView.State state) {
- mLayoutState.mExtra = getExtraLayoutSpace(state);
- mLayoutState.mLayoutDirection = layoutDirection;
- int fastScrollSpace;
- if (layoutDirection == LayoutState.LAYOUT_END) {
- mLayoutState.mExtra += mOrientationHelper.getEndPadding();
- // get the first child in the direction we are going
- final View child = getChildClosestToEnd();
- // the direction in which we are traversing children
- mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
- : LayoutState.ITEM_DIRECTION_TAIL;
- mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
- mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
- // calculate how much we can scroll without adding new children (independent of layout)
- fastScrollSpace = mOrientationHelper.getDecoratedEnd(child)
- - mOrientationHelper.getEndAfterPadding();
-
- } else {
- final View child = getChildClosestToStart();
- mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
- mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
- : LayoutState.ITEM_DIRECTION_HEAD;
- mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
- mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
- fastScrollSpace = -mOrientationHelper.getDecoratedStart(child)
- + mOrientationHelper.getStartAfterPadding();
- }
- mLayoutState.mAvailable = requiredSpace;
- if (canUseExistingSpace) {
- mLayoutState.mAvailable -= fastScrollSpace;
- }
- mLayoutState.mScrollingOffset = fastScrollSpace;
- }
-
- int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
- if (getChildCount() == 0 || dy == 0) {
- return 0;
- }
- mLayoutState.mRecycle = true;
- ensureLayoutState();
- final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
- final int absDy = Math.abs(dy);
- updateLayoutState(layoutDirection, absDy, true, state);
- final int freeScroll = mLayoutState.mScrollingOffset;
- final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
- if (consumed < 0) {
- if (DEBUG) {
- Log.d(TAG, "Don't have any more elements to scroll");
- }
- return 0;
- }
- final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
- mOrientationHelper.offsetChildren(-scrolled);
- if (DEBUG) {
- Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
- }
- return scrolled;
- }
-
- @Override
- public void assertNotInLayoutOrScroll(String message) {
- if (mPendingSavedState == null) {
- super.assertNotInLayoutOrScroll(message);
- }
- }
-
- /**
- * Recycles children between given indices.
- *
- * @param startIndex inclusive
- * @param endIndex exclusive
- */
- private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
- if (startIndex == endIndex) {
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
- }
- if (endIndex > startIndex) {
- for (int i = endIndex - 1; i >= startIndex; i--) {
- removeAndRecycleViewAt(i, recycler);
- }
- } else {
- for (int i = startIndex; i > endIndex; i--) {
- removeAndRecycleViewAt(i, recycler);
- }
- }
- }
-
- /**
- * Recycles views that went out of bounds after scrolling towards the end of the layout.
- *
- * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
- * @param dt This can be used to add additional padding to the visible area. This is used
- * to
- * detect children that will go out of bounds after scrolling, without actually
- * moving them.
- */
- private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
- if (dt < 0) {
- if (DEBUG) {
- Log.d(TAG, "Called recycle from start with a negative value. This might happen"
- + " during layout changes but may be sign of a bug");
- }
- return;
- }
- // ignore padding, ViewGroup may not clip children.
- final int limit = dt;
- final int childCount = getChildCount();
- if (mShouldReverseLayout) {
- for (int i = childCount - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
- recycleChildren(recycler, childCount - 1, i);
- return;
- }
- }
- } else {
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
- recycleChildren(recycler, 0, i);
- return;
- }
- }
- }
- }
-
-
- /**
- * Recycles views that went out of bounds after scrolling towards the start of the layout.
- *
- * @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
- * @param dt This can be used to add additional padding to the visible area. This is used
- * to detect children that will go out of bounds after scrolling, without
- * actually moving them.
- */
- private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
- final int childCount = getChildCount();
- if (dt < 0) {
- if (DEBUG) {
- Log.d(TAG, "Called recycle from end with a negative value. This might happen"
- + " during layout changes but may be sign of a bug");
- }
- return;
- }
- final int limit = mOrientationHelper.getEnd() - dt;
- if (mShouldReverseLayout) {
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
- recycleChildren(recycler, 0, i);
- return;
- }
- }
- } else {
- for (int i = childCount - 1; i >= 0; i--) {
- View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
- recycleChildren(recycler, childCount - 1, i);
- return;
- }
- }
- }
-
- }
-
- /**
- * Helper method to call appropriate recycle method depending on current layout direction
- *
- * @param recycler Current recycler that is attached to RecyclerView
- * @param layoutState Current layout state. Right now, this object does not change but
- * we may consider moving it out of this view so passing around as a
- * parameter for now, rather than accessing {@link #mLayoutState}
- * @see #recycleViewsFromStart(android.support.v7.widget.RecyclerView.Recycler, int)
- * @see #recycleViewsFromEnd(android.support.v7.widget.RecyclerView.Recycler, int)
- * @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
- */
- private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
- if (!layoutState.mRecycle) {
- return;
- }
- if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
- recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
- } else {
- recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
- }
- }
-
- /**
- * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
- * independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager}
- * and with little change, can be made publicly available as a helper class.
- *
- * @param recycler Current recycler that is attached to RecyclerView
- * @param layoutState Configuration on how we should fill out the available space.
- * @param state Context passed by the RecyclerView to control scroll steps.
- * @param stopOnFocusable If true, filling stops in the first focusable new child
- * @return Number of pixels that it added. Useful for scoll functions.
- */
- int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
- RecyclerView.State state, boolean stopOnFocusable) {
- // max offset we should set is mFastScroll + available
- final int start = layoutState.mAvailable;
- if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
- // TODO ugly bug fix. should not happen
- if (layoutState.mAvailable < 0) {
- layoutState.mScrollingOffset += layoutState.mAvailable;
- }
- recycleByLayoutState(recycler, layoutState);
- }
- int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
- LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
- while (remainingSpace > 0 && layoutState.hasMore(state)) {
- layoutChunkResult.resetInternal();
- layoutChunk(recycler, state, layoutState, layoutChunkResult);
- if (layoutChunkResult.mFinished) {
- break;
- }
- layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
- /**
- * Consume the available space if:
- * * layoutChunk did not request to be ignored
- * * OR we are laying out scrap children
- * * OR we are not doing pre-layout
- */
- if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
- || !state.isPreLayout()) {
- layoutState.mAvailable -= layoutChunkResult.mConsumed;
- // we keep a separate remaining space because mAvailable is important for recycling
- remainingSpace -= layoutChunkResult.mConsumed;
- }
-
- if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) {
- layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
- if (layoutState.mAvailable < 0) {
- layoutState.mScrollingOffset += layoutState.mAvailable;
- }
- recycleByLayoutState(recycler, layoutState);
- }
- if (stopOnFocusable && layoutChunkResult.mFocusable) {
- break;
- }
- }
- if (DEBUG) {
- validateChildOrder();
- }
- return start - layoutState.mAvailable;
- }
-
- void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
- LayoutState layoutState, LayoutChunkResult result) {
- View view = layoutState.next(recycler);
- if (view == null) {
- if (DEBUG && layoutState.mScrapList == null) {
- throw new RuntimeException("received null view when unexpected");
- }
- // if we are laying out views in scrap, this may return null which means there is
- // no more items to layout.
- result.mFinished = true;
- return;
- }
- RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
- if (layoutState.mScrapList == null) {
- if (mShouldReverseLayout == (layoutState.mLayoutDirection
- == LayoutState.LAYOUT_START)) {
- addView(view);
- } else {
- addView(view, 0);
- }
- } else {
- if (mShouldReverseLayout == (layoutState.mLayoutDirection
- == LayoutState.LAYOUT_START)) {
- addDisappearingView(view);
- } else {
- addDisappearingView(view, 0);
- }
- }
- measureChildWithMargins(view, 0, 0);
- result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
- int left, top, right, bottom;
- if (mOrientation == VERTICAL) {
- if (isLayoutRTL()) {
- right = getWidth() - getPaddingRight();
- left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
- } else {
- left = getPaddingLeft();
- right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
- }
- if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
- bottom = layoutState.mOffset;
- top = layoutState.mOffset - result.mConsumed;
- } else {
- top = layoutState.mOffset;
- bottom = layoutState.mOffset + result.mConsumed;
- }
- } else {
- top = getPaddingTop();
- bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
-
- if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
- right = layoutState.mOffset;
- left = layoutState.mOffset - result.mConsumed;
- } else {
- left = layoutState.mOffset;
- right = layoutState.mOffset + result.mConsumed;
- }
- }
- // We calculate everything with View's bounding box (which includes decor and margins)
- // To calculate correct layout position, we subtract margins.
- layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
- right - params.rightMargin, bottom - params.bottomMargin);
- if (DEBUG) {
- Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
- + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
- + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
- }
- // Consume the available space if the view is not removed OR changed
- if (params.isItemRemoved() || params.isItemChanged()) {
- result.mIgnoreConsumed = true;
- }
- result.mFocusable = view.isFocusable();
- }
-
- /**
- * Converts a focusDirection to orientation.
- *
- * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
- * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
- * {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
- * or 0 for not applicable
- * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
- * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
- */
- private int convertFocusDirectionToLayoutDirection(int focusDirection) {
- switch (focusDirection) {
- case View.FOCUS_BACKWARD:
- return LayoutState.LAYOUT_START;
- case View.FOCUS_FORWARD:
- return LayoutState.LAYOUT_END;
- case View.FOCUS_UP:
- return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
- : LayoutState.INVALID_LAYOUT;
- case View.FOCUS_DOWN:
- return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
- : LayoutState.INVALID_LAYOUT;
- case View.FOCUS_LEFT:
- return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
- : LayoutState.INVALID_LAYOUT;
- case View.FOCUS_RIGHT:
- return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
- : LayoutState.INVALID_LAYOUT;
- default:
- if (DEBUG) {
- Log.d(TAG, "Unknown focus request:" + focusDirection);
- }
- return LayoutState.INVALID_LAYOUT;
- }
-
- }
-
- /**
- * Convenience method to find the child closes to start. Caller should check it has enough
- * children.
- *
- * @return The child closes to start of the layout from user's perspective.
- */
- private View getChildClosestToStart() {
- return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
- }
-
- /**
- * Convenience method to find the child closes to end. Caller should check it has enough
- * children.
- *
- * @return The child closes to end of the layout from user's perspective.
- */
- private View getChildClosestToEnd() {
- return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
- }
-
-
- /**
- * Among the children that are suitable to be considered as an anchor child, returns the one
- * closest to the end of the layout.
- *
- * Due to ambiguous adapter updates or children being removed, some children's positions may be
- * invalid. This method is a best effort to find a position within adapter bounds if possible.
- *
- * It also prioritizes children that are within the visible bounds.
- *
- * @return A View that can be used an an anchor View.
- */
- private View findReferenceChildClosestToEnd(RecyclerView.State state) {
- return mShouldReverseLayout ? findFirstReferenceChild(state.getItemCount()) :
- findLastReferenceChild(state.getItemCount());
- }
-
- /**
- * Among the children that are suitable to be considered as an anchor child, returns the one
- * closest to the start of the layout.
- *
- * Due to ambiguous adapter updates or children being removed, some children's positions may be
- * invalid. This method is a best effort to find a position within adapter bounds if possible.
- *
- * It also prioritizes children that are within the visible bounds.
- *
- * @return A View that can be used an an anchor View.
- */
- private View findReferenceChildClosestToStart(RecyclerView.State state) {
- return mShouldReverseLayout ? findLastReferenceChild(state.getItemCount()) :
- findFirstReferenceChild(state.getItemCount());
- }
-
- private View findFirstReferenceChild(int itemCount) {
- return findReferenceChild(0, getChildCount(), itemCount);
- }
-
- private View findLastReferenceChild(int itemCount) {
- return findReferenceChild(getChildCount() - 1, -1, itemCount);
- }
-
- private View findReferenceChild(int start, int end, int itemCount) {
- ensureLayoutState();
- View invalidMatch = null;
- View outOfBoundsMatch = null;
- final int boundsStart = mOrientationHelper.getStartAfterPadding();
- final int boundsEnd = mOrientationHelper.getEndAfterPadding();
- final int diff = end > start ? 1 : -1;
- for (int i = start; i != end; i += diff) {
- final View view = getChildAt(i);
- final int position = getPosition(view);
- if (position >= 0 && position < itemCount) {
- if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
- if (invalidMatch == null) {
- invalidMatch = view; // removed item, least preferred
- }
- } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd ||
- mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
- if (outOfBoundsMatch == null) {
- outOfBoundsMatch = view; // item is not visible, less preferred
- }
- } else {
- return view;
- }
- }
- }
- return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
- }
-
- /**
- * Returns the adapter position of the first visible view.
- *
- * Note that, this value is not affected by layout orientation or item order traversal.
- * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
- * not in the layout.
- *
- * If RecyclerView has item decorators, they will be considered in calculations as well.
- *
- * LayoutManager may pre-cache some views that are not necessarily visible. Those views
- * are ignored in this method.
- *
- * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
- * there aren't any visible items.
- * @see #findFirstCompletelyVisibleItemPosition()
- * @see #findLastVisibleItemPosition()
- */
- public int findFirstVisibleItemPosition() {
- final View child = findOneVisibleChild(0, getChildCount(), false);
- return child == null ? NO_POSITION : getPosition(child);
- }
-
- /**
- * Returns the adapter position of the first fully visible view.
- *
- * Note that bounds check is only performed in the current orientation. That means, if
- * LayoutManager is horizontal, it will only check the view's left and right edges.
- *
- * @return The adapter position of the first fully visible item or
- * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
- * @see #findFirstVisibleItemPosition()
- * @see #findLastCompletelyVisibleItemPosition()
- */
- public int findFirstCompletelyVisibleItemPosition() {
- final View child = findOneVisibleChild(0, getChildCount(), true);
- return child == null ? NO_POSITION : getPosition(child);
- }
-
- /**
- * Returns the adapter position of the last visible view.
- *
- * Note that, this value is not affected by layout orientation or item order traversal.
- * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
- * not in the layout.
- *
- * If RecyclerView has item decorators, they will be considered in calculations as well.
- *
- * LayoutManager may pre-cache some views that are not necessarily visible. Those views
- * are ignored in this method.
- *
- * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
- * there aren't any visible items.
- * @see #findLastCompletelyVisibleItemPosition()
- * @see #findFirstVisibleItemPosition()
- */
- public int findLastVisibleItemPosition() {
- final View child = findOneVisibleChild(getChildCount() - 1, -1, false);
- return child == null ? NO_POSITION : getPosition(child);
- }
-
- /**
- * Returns the adapter position of the last fully visible view.
- *
- * Note that bounds check is only performed in the current orientation. That means, if
- * LayoutManager is horizontal, it will only check the view's left and right edges.
- *
- * @return The adapter position of the last fully visible view or
- * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
- * @see #findLastVisibleItemPosition()
- * @see #findFirstCompletelyVisibleItemPosition()
- */
- public int findLastCompletelyVisibleItemPosition() {
- final View child = findOneVisibleChild(getChildCount() - 1, -1, true);
- return child == null ? NO_POSITION : getPosition(child);
- }
-
- View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
- ensureLayoutState();
- final int start = mOrientationHelper.getStartAfterPadding();
- final int end = mOrientationHelper.getEndAfterPadding();
- final int next = toIndex > fromIndex ? 1 : -1;
- for (int i = fromIndex; i != toIndex; i += next) {
- final View child = getChildAt(i);
- final int childStart = mOrientationHelper.getDecoratedStart(child);
- final int childEnd = mOrientationHelper.getDecoratedEnd(child);
- if (childStart < end && childEnd > start) {
- if (completelyVisible) {
- if (childStart >= start && childEnd <= end) {
- return child;
- }
- } else {
- return child;
- }
- }
- }
- return null;
- }
-
- @Override
- public View onFocusSearchFailed(View focused, int focusDirection,
- RecyclerView.Recycler recycler, RecyclerView.State state) {
- resolveShouldLayoutReverse();
- if (getChildCount() == 0) {
- return null;
- }
-
- final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
- if (layoutDir == LayoutState.INVALID_LAYOUT) {
- return null;
- }
- ensureLayoutState();
- final View referenceChild;
- if (layoutDir == LayoutState.LAYOUT_START) {
- referenceChild = findReferenceChildClosestToStart(state);
- } else {
- referenceChild = findReferenceChildClosestToEnd(state);
- }
- if (referenceChild == null) {
- if (DEBUG) {
- Log.d(TAG,
- "Cannot find a child with a valid position to be used for focus search.");
- }
- return null;
- }
- ensureLayoutState();
- final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
- updateLayoutState(layoutDir, maxScroll, false, state);
- mLayoutState.mScrollingOffset = LayoutState.SCOLLING_OFFSET_NaN;
- mLayoutState.mRecycle = false;
- fill(recycler, mLayoutState, state, true);
- final View nextFocus;
- if (layoutDir == LayoutState.LAYOUT_START) {
- nextFocus = getChildClosestToStart();
- } else {
- nextFocus = getChildClosestToEnd();
- }
- if (nextFocus == referenceChild || !nextFocus.isFocusable()) {
- return null;
- }
- return nextFocus;
- }
-
- /**
- * Used for debugging.
- * Logs the internal representation of children to default logger.
- */
- private void logChildren() {
- Log.d(TAG, "internal representation of views on the screen");
- for (int i = 0; i < getChildCount(); i++) {
- View child = getChildAt(i);
- Log.d(TAG, "item " + getPosition(child) + ", coord:"
- + mOrientationHelper.getDecoratedStart(child));
- }
- Log.d(TAG, "==============");
- }
-
- /**
- * Used for debugging.
- * Validates that child views are laid out in correct order. This is important because rest of
- * the algorithm relies on this constraint.
- *
- * In default layout, child 0 should be closest to screen position 0 and last child should be
- * closest to position WIDTH or HEIGHT.
- * In reverse layout, last child should be closes to screen position 0 and first child should
- * be closest to position WIDTH or HEIGHT
- */
- void validateChildOrder() {
- Log.d(TAG, "validating child count " + getChildCount());
- if (getChildCount() < 1) {
- return;
- }
- int lastPos = getPosition(getChildAt(0));
- int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
- if (mShouldReverseLayout) {
- for (int i = 1; i < getChildCount(); i++) {
- View child = getChildAt(i);
- int pos = getPosition(child);
- int screenLoc = mOrientationHelper.getDecoratedStart(child);
- if (pos < lastPos) {
- logChildren();
- throw new RuntimeException("detected invalid position. loc invalid? " +
- (screenLoc < lastScreenLoc));
- }
- if (screenLoc > lastScreenLoc) {
- logChildren();
- throw new RuntimeException("detected invalid location");
- }
- }
- } else {
- for (int i = 1; i < getChildCount(); i++) {
- View child = getChildAt(i);
- int pos = getPosition(child);
- int screenLoc = mOrientationHelper.getDecoratedStart(child);
- if (pos < lastPos) {
- logChildren();
- throw new RuntimeException("detected invalid position. loc invalid? " +
- (screenLoc < lastScreenLoc));
- }
- if (screenLoc < lastScreenLoc) {
- logChildren();
- throw new RuntimeException("detected invalid location");
- }
- }
- }
- }
-
- @Override
- public boolean supportsPredictiveItemAnimations() {
- return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
- }
-
- /**
- * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
- * space.
- */
- static class LayoutState {
-
- final static String TAG = "LinearLayoutManager#LayoutState";
-
- final static int LAYOUT_START = -1;
-
- final static int LAYOUT_END = 1;
-
- final static int INVALID_LAYOUT = Integer.MIN_VALUE;
-
- final static int ITEM_DIRECTION_HEAD = -1;
-
- final static int ITEM_DIRECTION_TAIL = 1;
-
- final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
-
- /**
- * We may not want to recycle children in some cases (e.g. layout)
- */
- boolean mRecycle = true;
-
- /**
- * Pixel offset where layout should start
- */
- int mOffset;
-
- /**
- * Number of pixels that we should fill, in the layout direction.
- */
- int mAvailable;
-
- /**
- * Current position on the adapter to get the next item.
- */
- int mCurrentPosition;
-
- /**
- * Defines the direction in which the data adapter is traversed.
- * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
- */
- int mItemDirection;
-
- /**
- * Defines the direction in which the layout is filled.
- * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
- */
- int mLayoutDirection;
-
- /**
- * Used when LayoutState is constructed in a scrolling state.
- * It should be set the amount of scrolling we can make without creating a new view.
- * Settings this is required for efficient view recycling.
- */
- int mScrollingOffset;
-
- /**
- * Used if you want to pre-layout items that are not yet visible.
- * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
- * {@link #mExtra} is not considered to avoid recycling visible children.
- */
- int mExtra = 0;
-
- /**
- * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
- * is set to true, we skip removed views since they should not be laid out in post layout
- * step.
- */
- boolean mIsPreLayout = false;
-
- /**
- * When LLM needs to layout particular views, it sets this list in which case, LayoutState
- * will only return views from this list and return null if it cannot find an item.
- */
- List