Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright 2018 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package androidx.recyclerview.widget;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.content.Context;
     22 import android.graphics.PointF;
     23 import android.os.Parcel;
     24 import android.os.Parcelable;
     25 import android.util.AttributeSet;
     26 import android.util.Log;
     27 import android.view.View;
     28 import android.view.ViewGroup;
     29 import android.view.accessibility.AccessibilityEvent;
     30 
     31 import androidx.annotation.NonNull;
     32 import androidx.annotation.RestrictTo;
     33 import androidx.core.os.TraceCompat;
     34 import androidx.core.view.ViewCompat;
     35 
     36 import java.util.List;
     37 
     38 /**
     39  * A {@link RecyclerView.LayoutManager} implementation which provides
     40  * similar functionality to {@link android.widget.ListView}.
     41  */
     42 public class LinearLayoutManager extends RecyclerView.LayoutManager implements
     43         ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
     44 
     45     private static final String TAG = "LinearLayoutManager";
     46 
     47     static final boolean DEBUG = false;
     48 
     49     public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
     50 
     51     public static final int VERTICAL = RecyclerView.VERTICAL;
     52 
     53     public static final int INVALID_OFFSET = Integer.MIN_VALUE;
     54 
     55 
     56     /**
     57      * While trying to find next view to focus, LayoutManager will not try to scroll more
     58      * than this factor times the total space of the list. If layout is vertical, total space is the
     59      * height minus padding, if layout is horizontal, total space is the width minus padding.
     60      */
     61     private static final float MAX_SCROLL_FACTOR = 1 / 3f;
     62 
     63     /**
     64      * Current orientation. Either {@link #HORIZONTAL} or {@link #VERTICAL}
     65      */
     66     @RecyclerView.Orientation
     67     int mOrientation = RecyclerView.DEFAULT_ORIENTATION;
     68 
     69     /**
     70      * Helper class that keeps temporary layout state.
     71      * It does not keep state after layout is complete but we still keep a reference to re-use
     72      * the same object.
     73      */
     74     private LayoutState mLayoutState;
     75 
     76     /**
     77      * Many calculations are made depending on orientation. To keep it clean, this interface
     78      * helps {@link LinearLayoutManager} make those decisions.
     79      */
     80     OrientationHelper mOrientationHelper;
     81 
     82     /**
     83      * We need to track this so that we can ignore current position when it changes.
     84      */
     85     private boolean mLastStackFromEnd;
     86 
     87 
     88     /**
     89      * Defines if layout should be calculated from end to start.
     90      *
     91      * @see #mShouldReverseLayout
     92      */
     93     private boolean mReverseLayout = false;
     94 
     95     /**
     96      * This keeps the final value for how LayoutManager should start laying out views.
     97      * It is calculated by checking {@link #getReverseLayout()} and View's layout direction.
     98      * {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} is run.
     99      */
    100     boolean mShouldReverseLayout = false;
    101 
    102     /**
    103      * Works the same way as {@link android.widget.AbsListView#setStackFromBottom(boolean)} and
    104      * it supports both orientations.
    105      * see {@link android.widget.AbsListView#setStackFromBottom(boolean)}
    106      */
    107     private boolean mStackFromEnd = false;
    108 
    109     /**
    110      * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
    111      * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
    112      */
    113     private boolean mSmoothScrollbarEnabled = true;
    114 
    115     /**
    116      * When LayoutManager needs to scroll to a position, it sets this variable and requests a
    117      * layout which will check this variable and re-layout accordingly.
    118      */
    119     int mPendingScrollPosition = RecyclerView.NO_POSITION;
    120 
    121     /**
    122      * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
    123      * called.
    124      */
    125     int mPendingScrollPositionOffset = INVALID_OFFSET;
    126 
    127     private boolean mRecycleChildrenOnDetach;
    128 
    129     SavedState mPendingSavedState = null;
    130 
    131     /**
    132      *  Re-used variable to keep anchor information on re-layout.
    133      *  Anchor position and coordinate defines the reference point for LLM while doing a layout.
    134      * */
    135     final AnchorInfo mAnchorInfo = new AnchorInfo();
    136 
    137     /**
    138      * Stashed to avoid allocation, currently only used in #fill()
    139      */
    140     private final LayoutChunkResult mLayoutChunkResult = new LayoutChunkResult();
    141 
    142     /**
    143      * Number of items to prefetch when first coming on screen with new data.
    144      */
    145     private int mInitialPrefetchItemCount = 2;
    146 
    147     /**
    148      * Creates a vertical LinearLayoutManager
    149      *
    150      * @param context Current context, will be used to access resources.
    151      */
    152     public LinearLayoutManager(Context context) {
    153         this(context, RecyclerView.DEFAULT_ORIENTATION, false);
    154     }
    155 
    156     /**
    157      * @param context       Current context, will be used to access resources.
    158      * @param orientation   Layout orientation. Should be {@link #HORIZONTAL} or {@link
    159      *                      #VERTICAL}.
    160      * @param reverseLayout When set to true, layouts from end to start.
    161      */
    162     public LinearLayoutManager(Context context, @RecyclerView.Orientation int orientation,
    163             boolean reverseLayout) {
    164         setOrientation(orientation);
    165         setReverseLayout(reverseLayout);
    166     }
    167 
    168     /**
    169      * Constructor used when layout manager is set in XML by RecyclerView attribute
    170      * "layoutManager". Defaults to vertical orientation.
    171      *
    172      * @attr ref androidx.recyclerview.R.styleable#RecyclerView_android_orientation
    173      * @attr ref androidx.recyclerview.R.styleable#RecyclerView_reverseLayout
    174      * @attr ref androidx.recyclerview.R.styleable#RecyclerView_stackFromEnd
    175      */
    176     public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
    177             int defStyleRes) {
    178         Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
    179         setOrientation(properties.orientation);
    180         setReverseLayout(properties.reverseLayout);
    181         setStackFromEnd(properties.stackFromEnd);
    182     }
    183 
    184     @Override
    185     public boolean isAutoMeasureEnabled() {
    186         return true;
    187     }
    188 
    189     /**
    190      * {@inheritDoc}
    191      */
    192     @Override
    193     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
    194         return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
    195                 ViewGroup.LayoutParams.WRAP_CONTENT);
    196     }
    197 
    198     /**
    199      * Returns whether LayoutManager will recycle its children when it is detached from
    200      * RecyclerView.
    201      *
    202      * @return true if LayoutManager will recycle its children when it is detached from
    203      * RecyclerView.
    204      */
    205     public boolean getRecycleChildrenOnDetach() {
    206         return mRecycleChildrenOnDetach;
    207     }
    208 
    209     /**
    210      * Set whether LayoutManager will recycle its children when it is detached from
    211      * RecyclerView.
    212      * <p>
    213      * If you are using a {@link RecyclerView.RecycledViewPool}, it might be a good idea to set
    214      * this flag to <code>true</code> so that views will be available to other RecyclerViews
    215      * immediately.
    216      * <p>
    217      * Note that, setting this flag will result in a performance drop if RecyclerView
    218      * is restored.
    219      *
    220      * @param recycleChildrenOnDetach Whether children should be recycled in detach or not.
    221      */
    222     public void setRecycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
    223         mRecycleChildrenOnDetach = recycleChildrenOnDetach;
    224     }
    225 
    226     @Override
    227     public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
    228         super.onDetachedFromWindow(view, recycler);
    229         if (mRecycleChildrenOnDetach) {
    230             removeAndRecycleAllViews(recycler);
    231             recycler.clear();
    232         }
    233     }
    234 
    235     @Override
    236     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
    237         super.onInitializeAccessibilityEvent(event);
    238         if (getChildCount() > 0) {
    239             event.setFromIndex(findFirstVisibleItemPosition());
    240             event.setToIndex(findLastVisibleItemPosition());
    241         }
    242     }
    243 
    244     @Override
    245     public Parcelable onSaveInstanceState() {
    246         if (mPendingSavedState != null) {
    247             return new SavedState(mPendingSavedState);
    248         }
    249         SavedState state = new SavedState();
    250         if (getChildCount() > 0) {
    251             ensureLayoutState();
    252             boolean didLayoutFromEnd = mLastStackFromEnd ^ mShouldReverseLayout;
    253             state.mAnchorLayoutFromEnd = didLayoutFromEnd;
    254             if (didLayoutFromEnd) {
    255                 final View refChild = getChildClosestToEnd();
    256                 state.mAnchorOffset = mOrientationHelper.getEndAfterPadding()
    257                         - mOrientationHelper.getDecoratedEnd(refChild);
    258                 state.mAnchorPosition = getPosition(refChild);
    259             } else {
    260                 final View refChild = getChildClosestToStart();
    261                 state.mAnchorPosition = getPosition(refChild);
    262                 state.mAnchorOffset = mOrientationHelper.getDecoratedStart(refChild)
    263                         - mOrientationHelper.getStartAfterPadding();
    264             }
    265         } else {
    266             state.invalidateAnchor();
    267         }
    268         return state;
    269     }
    270 
    271     @Override
    272     public void onRestoreInstanceState(Parcelable state) {
    273         if (state instanceof SavedState) {
    274             mPendingSavedState = (SavedState) state;
    275             requestLayout();
    276             if (DEBUG) {
    277                 Log.d(TAG, "loaded saved state");
    278             }
    279         } else if (DEBUG) {
    280             Log.d(TAG, "invalid saved state class");
    281         }
    282     }
    283 
    284     /**
    285      * @return true if {@link #getOrientation()} is {@link #HORIZONTAL}
    286      */
    287     @Override
    288     public boolean canScrollHorizontally() {
    289         return mOrientation == HORIZONTAL;
    290     }
    291 
    292     /**
    293      * @return true if {@link #getOrientation()} is {@link #VERTICAL}
    294      */
    295     @Override
    296     public boolean canScrollVertically() {
    297         return mOrientation == VERTICAL;
    298     }
    299 
    300     /**
    301      * Compatibility support for {@link android.widget.AbsListView#setStackFromBottom(boolean)}
    302      */
    303     public void setStackFromEnd(boolean stackFromEnd) {
    304         assertNotInLayoutOrScroll(null);
    305         if (mStackFromEnd == stackFromEnd) {
    306             return;
    307         }
    308         mStackFromEnd = stackFromEnd;
    309         requestLayout();
    310     }
    311 
    312     public boolean getStackFromEnd() {
    313         return mStackFromEnd;
    314     }
    315 
    316     /**
    317      * Returns the current orientation of the layout.
    318      *
    319      * @return Current orientation,  either {@link #HORIZONTAL} or {@link #VERTICAL}
    320      * @see #setOrientation(int)
    321      */
    322     @RecyclerView.Orientation
    323     public int getOrientation() {
    324         return mOrientation;
    325     }
    326 
    327     /**
    328      * Sets the orientation of the layout. {@link LinearLayoutManager}
    329      * will do its best to keep scroll position.
    330      *
    331      * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
    332      */
    333     public void setOrientation(@RecyclerView.Orientation int orientation) {
    334         if (orientation != HORIZONTAL && orientation != VERTICAL) {
    335             throw new IllegalArgumentException("invalid orientation:" + orientation);
    336         }
    337 
    338         assertNotInLayoutOrScroll(null);
    339 
    340         if (orientation != mOrientation || mOrientationHelper == null) {
    341             mOrientationHelper =
    342                     OrientationHelper.createOrientationHelper(this, orientation);
    343             mAnchorInfo.mOrientationHelper = mOrientationHelper;
    344             mOrientation = orientation;
    345             requestLayout();
    346         }
    347     }
    348 
    349     /**
    350      * Calculates the view layout order. (e.g. from end to start or start to end)
    351      * RTL layout support is applied automatically. So if layout is RTL and
    352      * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
    353      */
    354     private void resolveShouldLayoutReverse() {
    355         // A == B is the same result, but we rather keep it readable
    356         if (mOrientation == VERTICAL || !isLayoutRTL()) {
    357             mShouldReverseLayout = mReverseLayout;
    358         } else {
    359             mShouldReverseLayout = !mReverseLayout;
    360         }
    361     }
    362 
    363     /**
    364      * Returns if views are laid out from the opposite direction of the layout.
    365      *
    366      * @return If layout is reversed or not.
    367      * @see #setReverseLayout(boolean)
    368      */
    369     public boolean getReverseLayout() {
    370         return mReverseLayout;
    371     }
    372 
    373     /**
    374      * Used to reverse item traversal and layout order.
    375      * This behaves similar to the layout change for RTL views. When set to true, first item is
    376      * laid out at the end of the UI, second item is laid out before it etc.
    377      *
    378      * For horizontal layouts, it depends on the layout direction.
    379      * When set to true, If {@link RecyclerView} is LTR, than it will
    380      * layout from RTL, if {@link RecyclerView}} is RTL, it will layout
    381      * from LTR.
    382      *
    383      * If you are looking for the exact same behavior of
    384      * {@link android.widget.AbsListView#setStackFromBottom(boolean)}, use
    385      * {@link #setStackFromEnd(boolean)}
    386      */
    387     public void setReverseLayout(boolean reverseLayout) {
    388         assertNotInLayoutOrScroll(null);
    389         if (reverseLayout == mReverseLayout) {
    390             return;
    391         }
    392         mReverseLayout = reverseLayout;
    393         requestLayout();
    394     }
    395 
    396     /**
    397      * {@inheritDoc}
    398      */
    399     @Override
    400     public View findViewByPosition(int position) {
    401         final int childCount = getChildCount();
    402         if (childCount == 0) {
    403             return null;
    404         }
    405         final int firstChild = getPosition(getChildAt(0));
    406         final int viewPosition = position - firstChild;
    407         if (viewPosition >= 0 && viewPosition < childCount) {
    408             final View child = getChildAt(viewPosition);
    409             if (getPosition(child) == position) {
    410                 return child; // in pre-layout, this may not match
    411             }
    412         }
    413         // fallback to traversal. This might be necessary in pre-layout.
    414         return super.findViewByPosition(position);
    415     }
    416 
    417     /**
    418      * <p>Returns the amount of extra space that should be laid out by LayoutManager.</p>
    419      *
    420      * <p>By default, {@link LinearLayoutManager} lays out 1 extra page
    421      * of items while smooth scrolling and 0 otherwise. You can override this method to implement
    422      * your custom layout pre-cache logic.</p>
    423      *
    424      * <p><strong>Note:</strong>Laying out invisible elements generally comes with significant
    425      * performance cost. It's typically only desirable in places like smooth scrolling to an unknown
    426      * location, where 1) the extra content helps LinearLayoutManager know in advance when its
    427      * target is approaching, so it can decelerate early and smoothly and 2) while motion is
    428      * continuous.</p>
    429      *
    430      * <p>Extending the extra layout space is especially expensive if done while the user may change
    431      * scrolling direction. Changing direction will cause the extra layout space to swap to the
    432      * opposite side of the viewport, incurring many rebinds/recycles, unless the cache is large
    433      * enough to handle it.</p>
    434      *
    435      * @return The extra space that should be laid out (in pixels).
    436      */
    437     protected int getExtraLayoutSpace(RecyclerView.State state) {
    438         if (state.hasTargetScrollPosition()) {
    439             return mOrientationHelper.getTotalSpace();
    440         } else {
    441             return 0;
    442         }
    443     }
    444 
    445     @Override
    446     public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
    447             int position) {
    448         LinearSmoothScroller linearSmoothScroller =
    449                 new LinearSmoothScroller(recyclerView.getContext());
    450         linearSmoothScroller.setTargetPosition(position);
    451         startSmoothScroll(linearSmoothScroller);
    452     }
    453 
    454     @Override
    455     public PointF computeScrollVectorForPosition(int targetPosition) {
    456         if (getChildCount() == 0) {
    457             return null;
    458         }
    459         final int firstChildPos = getPosition(getChildAt(0));
    460         final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;
    461         if (mOrientation == HORIZONTAL) {
    462             return new PointF(direction, 0);
    463         } else {
    464             return new PointF(0, direction);
    465         }
    466     }
    467 
    468     /**
    469      * {@inheritDoc}
    470      */
    471     @Override
    472     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    473         // layout algorithm:
    474         // 1) by checking children and other variables, find an anchor coordinate and an anchor
    475         //  item position.
    476         // 2) fill towards start, stacking from bottom
    477         // 3) fill towards end, stacking from top
    478         // 4) scroll to fulfill requirements like stack from bottom.
    479         // create layout state
    480         if (DEBUG) {
    481             Log.d(TAG, "is pre layout:" + state.isPreLayout());
    482         }
    483         if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
    484             if (state.getItemCount() == 0) {
    485                 removeAndRecycleAllViews(recycler);
    486                 return;
    487             }
    488         }
    489         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
    490             mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
    491         }
    492 
    493         ensureLayoutState();
    494         mLayoutState.mRecycle = false;
    495         // resolve layout direction
    496         resolveShouldLayoutReverse();
    497 
    498         final View focused = getFocusedChild();
    499         if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
    500                 || mPendingSavedState != null) {
    501             mAnchorInfo.reset();
    502             mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
    503             // calculate anchor position and coordinate
    504             updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
    505             mAnchorInfo.mValid = true;
    506         } else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
    507                         >= mOrientationHelper.getEndAfterPadding()
    508                 || mOrientationHelper.getDecoratedEnd(focused)
    509                 <= mOrientationHelper.getStartAfterPadding())) {
    510             // This case relates to when the anchor child is the focused view and due to layout
    511             // shrinking the focused view fell outside the viewport, e.g. when soft keyboard shows
    512             // up after tapping an EditText which shrinks RV causing the focused view (The tapped
    513             // EditText which is the anchor child) to get kicked out of the screen. Will update the
    514             // anchor coordinate in order to make sure that the focused view is laid out. Otherwise,
    515             // the available space in layoutState will be calculated as negative preventing the
    516             // focused view from being laid out in fill.
    517             // Note that we won't update the anchor position between layout passes (refer to
    518             // TestResizingRelayoutWithAutoMeasure), which happens if we were to call
    519             // updateAnchorInfoForLayout for an anchor that's not the focused view (e.g. a reference
    520             // child which can change between layout passes).
    521             mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
    522         }
    523         if (DEBUG) {
    524             Log.d(TAG, "Anchor info:" + mAnchorInfo);
    525         }
    526 
    527         // LLM may decide to layout items for "extra" pixels to account for scrolling target,
    528         // caching or predictive animations.
    529         int extraForStart;
    530         int extraForEnd;
    531         final int extra = getExtraLayoutSpace(state);
    532         // If the previous scroll delta was less than zero, the extra space should be laid out
    533         // at the start. Otherwise, it should be at the end.
    534         if (mLayoutState.mLastScrollDelta >= 0) {
    535             extraForEnd = extra;
    536             extraForStart = 0;
    537         } else {
    538             extraForStart = extra;
    539             extraForEnd = 0;
    540         }
    541         extraForStart += mOrientationHelper.getStartAfterPadding();
    542         extraForEnd += mOrientationHelper.getEndPadding();
    543         if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
    544                 && mPendingScrollPositionOffset != INVALID_OFFSET) {
    545             // if the child is visible and we are going to move it around, we should layout
    546             // extra items in the opposite direction to make sure new items animate nicely
    547             // instead of just fading in
    548             final View existing = findViewByPosition(mPendingScrollPosition);
    549             if (existing != null) {
    550                 final int current;
    551                 final int upcomingOffset;
    552                 if (mShouldReverseLayout) {
    553                     current = mOrientationHelper.getEndAfterPadding()
    554                             - mOrientationHelper.getDecoratedEnd(existing);
    555                     upcomingOffset = current - mPendingScrollPositionOffset;
    556                 } else {
    557                     current = mOrientationHelper.getDecoratedStart(existing)
    558                             - mOrientationHelper.getStartAfterPadding();
    559                     upcomingOffset = mPendingScrollPositionOffset - current;
    560                 }
    561                 if (upcomingOffset > 0) {
    562                     extraForStart += upcomingOffset;
    563                 } else {
    564                     extraForEnd -= upcomingOffset;
    565                 }
    566             }
    567         }
    568         int startOffset;
    569         int endOffset;
    570         final int firstLayoutDirection;
    571         if (mAnchorInfo.mLayoutFromEnd) {
    572             firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
    573                     : LayoutState.ITEM_DIRECTION_HEAD;
    574         } else {
    575             firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
    576                     : LayoutState.ITEM_DIRECTION_TAIL;
    577         }
    578 
    579         onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
    580         detachAndScrapAttachedViews(recycler);
    581         mLayoutState.mInfinite = resolveIsInfinite();
    582         mLayoutState.mIsPreLayout = state.isPreLayout();
    583         if (mAnchorInfo.mLayoutFromEnd) {
    584             // fill towards start
    585             updateLayoutStateToFillStart(mAnchorInfo);
    586             mLayoutState.mExtra = extraForStart;
    587             fill(recycler, mLayoutState, state, false);
    588             startOffset = mLayoutState.mOffset;
    589             final int firstElement = mLayoutState.mCurrentPosition;
    590             if (mLayoutState.mAvailable > 0) {
    591                 extraForEnd += mLayoutState.mAvailable;
    592             }
    593             // fill towards end
    594             updateLayoutStateToFillEnd(mAnchorInfo);
    595             mLayoutState.mExtra = extraForEnd;
    596             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
    597             fill(recycler, mLayoutState, state, false);
    598             endOffset = mLayoutState.mOffset;
    599 
    600             if (mLayoutState.mAvailable > 0) {
    601                 // end could not consume all. add more items towards start
    602                 extraForStart = mLayoutState.mAvailable;
    603                 updateLayoutStateToFillStart(firstElement, startOffset);
    604                 mLayoutState.mExtra = extraForStart;
    605                 fill(recycler, mLayoutState, state, false);
    606                 startOffset = mLayoutState.mOffset;
    607             }
    608         } else {
    609             // fill towards end
    610             updateLayoutStateToFillEnd(mAnchorInfo);
    611             mLayoutState.mExtra = extraForEnd;
    612             fill(recycler, mLayoutState, state, false);
    613             endOffset = mLayoutState.mOffset;
    614             final int lastElement = mLayoutState.mCurrentPosition;
    615             if (mLayoutState.mAvailable > 0) {
    616                 extraForStart += mLayoutState.mAvailable;
    617             }
    618             // fill towards start
    619             updateLayoutStateToFillStart(mAnchorInfo);
    620             mLayoutState.mExtra = extraForStart;
    621             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
    622             fill(recycler, mLayoutState, state, false);
    623             startOffset = mLayoutState.mOffset;
    624 
    625             if (mLayoutState.mAvailable > 0) {
    626                 extraForEnd = mLayoutState.mAvailable;
    627                 // start could not consume all it should. add more items towards end
    628                 updateLayoutStateToFillEnd(lastElement, endOffset);
    629                 mLayoutState.mExtra = extraForEnd;
    630                 fill(recycler, mLayoutState, state, false);
    631                 endOffset = mLayoutState.mOffset;
    632             }
    633         }
    634 
    635         // changes may cause gaps on the UI, try to fix them.
    636         // TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
    637         // changed
    638         if (getChildCount() > 0) {
    639             // because layout from end may be changed by scroll to position
    640             // we re-calculate it.
    641             // find which side we should check for gaps.
    642             if (mShouldReverseLayout ^ mStackFromEnd) {
    643                 int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
    644                 startOffset += fixOffset;
    645                 endOffset += fixOffset;
    646                 fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
    647                 startOffset += fixOffset;
    648                 endOffset += fixOffset;
    649             } else {
    650                 int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
    651                 startOffset += fixOffset;
    652                 endOffset += fixOffset;
    653                 fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
    654                 startOffset += fixOffset;
    655                 endOffset += fixOffset;
    656             }
    657         }
    658         layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
    659         if (!state.isPreLayout()) {
    660             mOrientationHelper.onLayoutComplete();
    661         } else {
    662             mAnchorInfo.reset();
    663         }
    664         mLastStackFromEnd = mStackFromEnd;
    665         if (DEBUG) {
    666             validateChildOrder();
    667         }
    668     }
    669 
    670     @Override
    671     public void onLayoutCompleted(RecyclerView.State state) {
    672         super.onLayoutCompleted(state);
    673         mPendingSavedState = null; // we don't need this anymore
    674         mPendingScrollPosition = RecyclerView.NO_POSITION;
    675         mPendingScrollPositionOffset = INVALID_OFFSET;
    676         mAnchorInfo.reset();
    677     }
    678 
    679     /**
    680      * Method called when Anchor position is decided. Extending class can setup accordingly or
    681      * even update anchor info if necessary.
    682      * @param recycler The recycler for the layout
    683      * @param state The layout state
    684      * @param anchorInfo The mutable POJO that keeps the position and offset.
    685      * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
    686      *                                 indices.
    687      */
    688     void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
    689             AnchorInfo anchorInfo, int firstLayoutItemDirection) {
    690     }
    691 
    692     /**
    693      * If necessary, layouts new items for predictive animations
    694      */
    695     private void layoutForPredictiveAnimations(RecyclerView.Recycler recycler,
    696             RecyclerView.State state, int startOffset,
    697             int endOffset) {
    698         // If there are scrap children that we did not layout, we need to find where they did go
    699         // and layout them accordingly so that animations can work as expected.
    700         // This case may happen if new views are added or an existing view expands and pushes
    701         // another view out of bounds.
    702         if (!state.willRunPredictiveAnimations() ||  getChildCount() == 0 || state.isPreLayout()
    703                 || !supportsPredictiveItemAnimations()) {
    704             return;
    705         }
    706         // to make the logic simpler, we calculate the size of children and call fill.
    707         int scrapExtraStart = 0, scrapExtraEnd = 0;
    708         final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
    709         final int scrapSize = scrapList.size();
    710         final int firstChildPos = getPosition(getChildAt(0));
    711         for (int i = 0; i < scrapSize; i++) {
    712             RecyclerView.ViewHolder scrap = scrapList.get(i);
    713             if (scrap.isRemoved()) {
    714                 continue;
    715             }
    716             final int position = scrap.getLayoutPosition();
    717             final int direction = position < firstChildPos != mShouldReverseLayout
    718                     ? LayoutState.LAYOUT_START : LayoutState.LAYOUT_END;
    719             if (direction == LayoutState.LAYOUT_START) {
    720                 scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
    721             } else {
    722                 scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
    723             }
    724         }
    725 
    726         if (DEBUG) {
    727             Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart
    728                     + " towards start and " + scrapExtraEnd + " towards end");
    729         }
    730         mLayoutState.mScrapList = scrapList;
    731         if (scrapExtraStart > 0) {
    732             View anchor = getChildClosestToStart();
    733             updateLayoutStateToFillStart(getPosition(anchor), startOffset);
    734             mLayoutState.mExtra = scrapExtraStart;
    735             mLayoutState.mAvailable = 0;
    736             mLayoutState.assignPositionFromScrapList();
    737             fill(recycler, mLayoutState, state, false);
    738         }
    739 
    740         if (scrapExtraEnd > 0) {
    741             View anchor = getChildClosestToEnd();
    742             updateLayoutStateToFillEnd(getPosition(anchor), endOffset);
    743             mLayoutState.mExtra = scrapExtraEnd;
    744             mLayoutState.mAvailable = 0;
    745             mLayoutState.assignPositionFromScrapList();
    746             fill(recycler, mLayoutState, state, false);
    747         }
    748         mLayoutState.mScrapList = null;
    749     }
    750 
    751     private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
    752             AnchorInfo anchorInfo) {
    753         if (updateAnchorFromPendingData(state, anchorInfo)) {
    754             if (DEBUG) {
    755                 Log.d(TAG, "updated anchor info from pending information");
    756             }
    757             return;
    758         }
    759 
    760         if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
    761             if (DEBUG) {
    762                 Log.d(TAG, "updated anchor info from existing children");
    763             }
    764             return;
    765         }
    766         if (DEBUG) {
    767             Log.d(TAG, "deciding anchor info for fresh state");
    768         }
    769         anchorInfo.assignCoordinateFromPadding();
    770         anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
    771     }
    772 
    773     /**
    774      * Finds an anchor child from existing Views. Most of the time, this is the view closest to
    775      * start or end that has a valid position (e.g. not removed).
    776      * <p>
    777      * If a child has focus, it is given priority.
    778      */
    779     private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
    780             RecyclerView.State state, AnchorInfo anchorInfo) {
    781         if (getChildCount() == 0) {
    782             return false;
    783         }
    784         final View focused = getFocusedChild();
    785         if (focused != null && anchorInfo.isViewValidAsAnchor(focused, state)) {
    786             anchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
    787             return true;
    788         }
    789         if (mLastStackFromEnd != mStackFromEnd) {
    790             return false;
    791         }
    792         View referenceChild = anchorInfo.mLayoutFromEnd
    793                 ? findReferenceChildClosestToEnd(recycler, state)
    794                 : findReferenceChildClosestToStart(recycler, state);
    795         if (referenceChild != null) {
    796             anchorInfo.assignFromView(referenceChild, getPosition(referenceChild));
    797             // If all visible views are removed in 1 pass, reference child might be out of bounds.
    798             // If that is the case, offset it back to 0 so that we use these pre-layout children.
    799             if (!state.isPreLayout() && supportsPredictiveItemAnimations()) {
    800                 // validate this child is at least partially visible. if not, offset it to start
    801                 final boolean notVisible =
    802                         mOrientationHelper.getDecoratedStart(referenceChild) >= mOrientationHelper
    803                                 .getEndAfterPadding()
    804                                 || mOrientationHelper.getDecoratedEnd(referenceChild)
    805                                 < mOrientationHelper.getStartAfterPadding();
    806                 if (notVisible) {
    807                     anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
    808                             ? mOrientationHelper.getEndAfterPadding()
    809                             : mOrientationHelper.getStartAfterPadding();
    810                 }
    811             }
    812             return true;
    813         }
    814         return false;
    815     }
    816 
    817     /**
    818      * If there is a pending scroll position or saved states, updates the anchor info from that
    819      * data and returns true
    820      */
    821     private boolean updateAnchorFromPendingData(RecyclerView.State state, AnchorInfo anchorInfo) {
    822         if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
    823             return false;
    824         }
    825         // validate scroll position
    826         if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
    827             mPendingScrollPosition = RecyclerView.NO_POSITION;
    828             mPendingScrollPositionOffset = INVALID_OFFSET;
    829             if (DEBUG) {
    830                 Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
    831             }
    832             return false;
    833         }
    834 
    835         // if child is visible, try to make it a reference child and ensure it is fully visible.
    836         // if child is not visible, align it depending on its virtual position.
    837         anchorInfo.mPosition = mPendingScrollPosition;
    838         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
    839             // Anchor offset depends on how that child was laid out. Here, we update it
    840             // according to our current view bounds
    841             anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
    842             if (anchorInfo.mLayoutFromEnd) {
    843                 anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
    844                         - mPendingSavedState.mAnchorOffset;
    845             } else {
    846                 anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
    847                         + mPendingSavedState.mAnchorOffset;
    848             }
    849             return true;
    850         }
    851 
    852         if (mPendingScrollPositionOffset == INVALID_OFFSET) {
    853             View child = findViewByPosition(mPendingScrollPosition);
    854             if (child != null) {
    855                 final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
    856                 if (childSize > mOrientationHelper.getTotalSpace()) {
    857                     // item does not fit. fix depending on layout direction
    858                     anchorInfo.assignCoordinateFromPadding();
    859                     return true;
    860                 }
    861                 final int startGap = mOrientationHelper.getDecoratedStart(child)
    862                         - mOrientationHelper.getStartAfterPadding();
    863                 if (startGap < 0) {
    864                     anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding();
    865                     anchorInfo.mLayoutFromEnd = false;
    866                     return true;
    867                 }
    868                 final int endGap = mOrientationHelper.getEndAfterPadding()
    869                         - mOrientationHelper.getDecoratedEnd(child);
    870                 if (endGap < 0) {
    871                     anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding();
    872                     anchorInfo.mLayoutFromEnd = true;
    873                     return true;
    874                 }
    875                 anchorInfo.mCoordinate = anchorInfo.mLayoutFromEnd
    876                         ? (mOrientationHelper.getDecoratedEnd(child) + mOrientationHelper
    877                         .getTotalSpaceChange())
    878                         : mOrientationHelper.getDecoratedStart(child);
    879             } else { // item is not visible.
    880                 if (getChildCount() > 0) {
    881                     // get position of any child, does not matter
    882                     int pos = getPosition(getChildAt(0));
    883                     anchorInfo.mLayoutFromEnd = mPendingScrollPosition < pos
    884                             == mShouldReverseLayout;
    885                 }
    886                 anchorInfo.assignCoordinateFromPadding();
    887             }
    888             return true;
    889         }
    890         // override layout from end values for consistency
    891         anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
    892         // if this changes, we should update prepareForDrop as well
    893         if (mShouldReverseLayout) {
    894             anchorInfo.mCoordinate = mOrientationHelper.getEndAfterPadding()
    895                     - mPendingScrollPositionOffset;
    896         } else {
    897             anchorInfo.mCoordinate = mOrientationHelper.getStartAfterPadding()
    898                     + mPendingScrollPositionOffset;
    899         }
    900         return true;
    901     }
    902 
    903     /**
    904      * @return The final offset amount for children
    905      */
    906     private int fixLayoutEndGap(int endOffset, RecyclerView.Recycler recycler,
    907             RecyclerView.State state, boolean canOffsetChildren) {
    908         int gap = mOrientationHelper.getEndAfterPadding() - endOffset;
    909         int fixOffset = 0;
    910         if (gap > 0) {
    911             fixOffset = -scrollBy(-gap, recycler, state);
    912         } else {
    913             return 0; // nothing to fix
    914         }
    915         // move offset according to scroll amount
    916         endOffset += fixOffset;
    917         if (canOffsetChildren) {
    918             // re-calculate gap, see if we could fix it
    919             gap = mOrientationHelper.getEndAfterPadding() - endOffset;
    920             if (gap > 0) {
    921                 mOrientationHelper.offsetChildren(gap);
    922                 return gap + fixOffset;
    923             }
    924         }
    925         return fixOffset;
    926     }
    927 
    928     /**
    929      * @return The final offset amount for children
    930      */
    931     private int fixLayoutStartGap(int startOffset, RecyclerView.Recycler recycler,
    932             RecyclerView.State state, boolean canOffsetChildren) {
    933         int gap = startOffset - mOrientationHelper.getStartAfterPadding();
    934         int fixOffset = 0;
    935         if (gap > 0) {
    936             // check if we should fix this gap.
    937             fixOffset = -scrollBy(gap, recycler, state);
    938         } else {
    939             return 0; // nothing to fix
    940         }
    941         startOffset += fixOffset;
    942         if (canOffsetChildren) {
    943             // re-calculate gap, see if we could fix it
    944             gap = startOffset - mOrientationHelper.getStartAfterPadding();
    945             if (gap > 0) {
    946                 mOrientationHelper.offsetChildren(-gap);
    947                 return fixOffset - gap;
    948             }
    949         }
    950         return fixOffset;
    951     }
    952 
    953     private void updateLayoutStateToFillEnd(AnchorInfo anchorInfo) {
    954         updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);
    955     }
    956 
    957     private void updateLayoutStateToFillEnd(int itemPosition, int offset) {
    958         mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
    959         mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
    960                 LayoutState.ITEM_DIRECTION_TAIL;
    961         mLayoutState.mCurrentPosition = itemPosition;
    962         mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
    963         mLayoutState.mOffset = offset;
    964         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
    965     }
    966 
    967     private void updateLayoutStateToFillStart(AnchorInfo anchorInfo) {
    968         updateLayoutStateToFillStart(anchorInfo.mPosition, anchorInfo.mCoordinate);
    969     }
    970 
    971     private void updateLayoutStateToFillStart(int itemPosition, int offset) {
    972         mLayoutState.mAvailable = offset - mOrientationHelper.getStartAfterPadding();
    973         mLayoutState.mCurrentPosition = itemPosition;
    974         mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
    975                 LayoutState.ITEM_DIRECTION_HEAD;
    976         mLayoutState.mLayoutDirection = LayoutState.LAYOUT_START;
    977         mLayoutState.mOffset = offset;
    978         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
    979 
    980     }
    981 
    982     protected boolean isLayoutRTL() {
    983         return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
    984     }
    985 
    986     void ensureLayoutState() {
    987         if (mLayoutState == null) {
    988             mLayoutState = createLayoutState();
    989         }
    990     }
    991 
    992     /**
    993      * Test overrides this to plug some tracking and verification.
    994      *
    995      * @return A new LayoutState
    996      */
    997     LayoutState createLayoutState() {
    998         return new LayoutState();
    999     }
   1000 
   1001     /**
   1002      * <p>Scroll the RecyclerView to make the position visible.</p>
   1003      *
   1004      * <p>RecyclerView will scroll the minimum amount that is necessary to make the
   1005      * target position visible. If you are looking for a similar behavior to
   1006      * {@link android.widget.ListView#setSelection(int)} or
   1007      * {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
   1008      * {@link #scrollToPositionWithOffset(int, int)}.</p>
   1009      *
   1010      * <p>Note that scroll position change will not be reflected until the next layout call.</p>
   1011      *
   1012      * @param position Scroll to this adapter position
   1013      * @see #scrollToPositionWithOffset(int, int)
   1014      */
   1015     @Override
   1016     public void scrollToPosition(int position) {
   1017         mPendingScrollPosition = position;
   1018         mPendingScrollPositionOffset = INVALID_OFFSET;
   1019         if (mPendingSavedState != null) {
   1020             mPendingSavedState.invalidateAnchor();
   1021         }
   1022         requestLayout();
   1023     }
   1024 
   1025     /**
   1026      * Scroll to the specified adapter position with the given offset from resolved layout
   1027      * start. Resolved layout start depends on {@link #getReverseLayout()},
   1028      * {@link ViewCompat#getLayoutDirection(android.view.View)} and {@link #getStackFromEnd()}.
   1029      * <p>
   1030      * For example, if layout is {@link #VERTICAL} and {@link #getStackFromEnd()} is true, calling
   1031      * <code>scrollToPositionWithOffset(10, 20)</code> will layout such that
   1032      * <code>item[10]</code>'s bottom is 20 pixels above the RecyclerView's bottom.
   1033      * <p>
   1034      * Note that scroll position change will not be reflected until the next layout call.
   1035      * <p>
   1036      * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
   1037      *
   1038      * @param position Index (starting at 0) of the reference item.
   1039      * @param offset   The distance (in pixels) between the start edge of the item view and
   1040      *                 start edge of the RecyclerView.
   1041      * @see #setReverseLayout(boolean)
   1042      * @see #scrollToPosition(int)
   1043      */
   1044     public void scrollToPositionWithOffset(int position, int offset) {
   1045         mPendingScrollPosition = position;
   1046         mPendingScrollPositionOffset = offset;
   1047         if (mPendingSavedState != null) {
   1048             mPendingSavedState.invalidateAnchor();
   1049         }
   1050         requestLayout();
   1051     }
   1052 
   1053 
   1054     /**
   1055      * {@inheritDoc}
   1056      */
   1057     @Override
   1058     public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
   1059             RecyclerView.State state) {
   1060         if (mOrientation == VERTICAL) {
   1061             return 0;
   1062         }
   1063         return scrollBy(dx, recycler, state);
   1064     }
   1065 
   1066     /**
   1067      * {@inheritDoc}
   1068      */
   1069     @Override
   1070     public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
   1071             RecyclerView.State state) {
   1072         if (mOrientation == HORIZONTAL) {
   1073             return 0;
   1074         }
   1075         return scrollBy(dy, recycler, state);
   1076     }
   1077 
   1078     @Override
   1079     public int computeHorizontalScrollOffset(RecyclerView.State state) {
   1080         return computeScrollOffset(state);
   1081     }
   1082 
   1083     @Override
   1084     public int computeVerticalScrollOffset(RecyclerView.State state) {
   1085         return computeScrollOffset(state);
   1086     }
   1087 
   1088     @Override
   1089     public int computeHorizontalScrollExtent(RecyclerView.State state) {
   1090         return computeScrollExtent(state);
   1091     }
   1092 
   1093     @Override
   1094     public int computeVerticalScrollExtent(RecyclerView.State state) {
   1095         return computeScrollExtent(state);
   1096     }
   1097 
   1098     @Override
   1099     public int computeHorizontalScrollRange(RecyclerView.State state) {
   1100         return computeScrollRange(state);
   1101     }
   1102 
   1103     @Override
   1104     public int computeVerticalScrollRange(RecyclerView.State state) {
   1105         return computeScrollRange(state);
   1106     }
   1107 
   1108     private int computeScrollOffset(RecyclerView.State state) {
   1109         if (getChildCount() == 0) {
   1110             return 0;
   1111         }
   1112         ensureLayoutState();
   1113         return ScrollbarHelper.computeScrollOffset(state, mOrientationHelper,
   1114                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
   1115                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
   1116                 this, mSmoothScrollbarEnabled, mShouldReverseLayout);
   1117     }
   1118 
   1119     private int computeScrollExtent(RecyclerView.State state) {
   1120         if (getChildCount() == 0) {
   1121             return 0;
   1122         }
   1123         ensureLayoutState();
   1124         return ScrollbarHelper.computeScrollExtent(state, mOrientationHelper,
   1125                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
   1126                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
   1127                 this,  mSmoothScrollbarEnabled);
   1128     }
   1129 
   1130     private int computeScrollRange(RecyclerView.State state) {
   1131         if (getChildCount() == 0) {
   1132             return 0;
   1133         }
   1134         ensureLayoutState();
   1135         return ScrollbarHelper.computeScrollRange(state, mOrientationHelper,
   1136                 findFirstVisibleChildClosestToStart(!mSmoothScrollbarEnabled, true),
   1137                 findFirstVisibleChildClosestToEnd(!mSmoothScrollbarEnabled, true),
   1138                 this, mSmoothScrollbarEnabled);
   1139     }
   1140 
   1141     /**
   1142      * When smooth scrollbar is enabled, the position and size of the scrollbar thumb is computed
   1143      * based on the number of visible pixels in the visible items. This however assumes that all
   1144      * list items have similar or equal widths or heights (depending on list orientation).
   1145      * If you use a list in which items have different dimensions, the scrollbar will change
   1146      * appearance as the user scrolls through the list. To avoid this issue,  you need to disable
   1147      * this property.
   1148      *
   1149      * When smooth scrollbar is disabled, the position and size of the scrollbar thumb is based
   1150      * solely on the number of items in the adapter and the position of the visible items inside
   1151      * the adapter. This provides a stable scrollbar as the user navigates through a list of items
   1152      * with varying widths / heights.
   1153      *
   1154      * @param enabled Whether or not to enable smooth scrollbar.
   1155      *
   1156      * @see #setSmoothScrollbarEnabled(boolean)
   1157      */
   1158     public void setSmoothScrollbarEnabled(boolean enabled) {
   1159         mSmoothScrollbarEnabled = enabled;
   1160     }
   1161 
   1162     /**
   1163      * Returns the current state of the smooth scrollbar feature. It is enabled by default.
   1164      *
   1165      * @return True if smooth scrollbar is enabled, false otherwise.
   1166      *
   1167      * @see #setSmoothScrollbarEnabled(boolean)
   1168      */
   1169     public boolean isSmoothScrollbarEnabled() {
   1170         return mSmoothScrollbarEnabled;
   1171     }
   1172 
   1173     private void updateLayoutState(int layoutDirection, int requiredSpace,
   1174             boolean canUseExistingSpace, RecyclerView.State state) {
   1175         // If parent provides a hint, don't measure unlimited.
   1176         mLayoutState.mInfinite = resolveIsInfinite();
   1177         mLayoutState.mExtra = getExtraLayoutSpace(state);
   1178         mLayoutState.mLayoutDirection = layoutDirection;
   1179         int scrollingOffset;
   1180         if (layoutDirection == LayoutState.LAYOUT_END) {
   1181             mLayoutState.mExtra += mOrientationHelper.getEndPadding();
   1182             // get the first child in the direction we are going
   1183             final View child = getChildClosestToEnd();
   1184             // the direction in which we are traversing children
   1185             mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
   1186                     : LayoutState.ITEM_DIRECTION_TAIL;
   1187             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
   1188             mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
   1189             // calculate how much we can scroll without adding new children (independent of layout)
   1190             scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
   1191                     - mOrientationHelper.getEndAfterPadding();
   1192 
   1193         } else {
   1194             final View child = getChildClosestToStart();
   1195             mLayoutState.mExtra += mOrientationHelper.getStartAfterPadding();
   1196             mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
   1197                     : LayoutState.ITEM_DIRECTION_HEAD;
   1198             mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
   1199             mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
   1200             scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
   1201                     + mOrientationHelper.getStartAfterPadding();
   1202         }
   1203         mLayoutState.mAvailable = requiredSpace;
   1204         if (canUseExistingSpace) {
   1205             mLayoutState.mAvailable -= scrollingOffset;
   1206         }
   1207         mLayoutState.mScrollingOffset = scrollingOffset;
   1208     }
   1209 
   1210     boolean resolveIsInfinite() {
   1211         return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
   1212                 && mOrientationHelper.getEnd() == 0;
   1213     }
   1214 
   1215     void collectPrefetchPositionsForLayoutState(RecyclerView.State state, LayoutState layoutState,
   1216             LayoutPrefetchRegistry layoutPrefetchRegistry) {
   1217         final int pos = layoutState.mCurrentPosition;
   1218         if (pos >= 0 && pos < state.getItemCount()) {
   1219             layoutPrefetchRegistry.addPosition(pos, Math.max(0, layoutState.mScrollingOffset));
   1220         }
   1221     }
   1222 
   1223     @Override
   1224     public void collectInitialPrefetchPositions(int adapterItemCount,
   1225             LayoutPrefetchRegistry layoutPrefetchRegistry) {
   1226         final boolean fromEnd;
   1227         final int anchorPos;
   1228         if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
   1229             // use restored state, since it hasn't been resolved yet
   1230             fromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
   1231             anchorPos = mPendingSavedState.mAnchorPosition;
   1232         } else {
   1233             resolveShouldLayoutReverse();
   1234             fromEnd = mShouldReverseLayout;
   1235             if (mPendingScrollPosition == RecyclerView.NO_POSITION) {
   1236                 anchorPos = fromEnd ? adapterItemCount - 1 : 0;
   1237             } else {
   1238                 anchorPos = mPendingScrollPosition;
   1239             }
   1240         }
   1241 
   1242         final int direction = fromEnd
   1243                 ? LayoutState.ITEM_DIRECTION_HEAD
   1244                 : LayoutState.ITEM_DIRECTION_TAIL;
   1245         int targetPos = anchorPos;
   1246         for (int i = 0; i < mInitialPrefetchItemCount; i++) {
   1247             if (targetPos >= 0 && targetPos < adapterItemCount) {
   1248                 layoutPrefetchRegistry.addPosition(targetPos, 0);
   1249             } else {
   1250                 break; // no more to prefetch
   1251             }
   1252             targetPos += direction;
   1253         }
   1254     }
   1255 
   1256     /**
   1257      * Sets the number of items to prefetch in
   1258      * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
   1259      * how many inner items should be prefetched when this LayoutManager's RecyclerView
   1260      * is nested inside another RecyclerView.
   1261      *
   1262      * <p>Set this value to the number of items this inner LayoutManager will display when it is
   1263      * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
   1264      * so they are ready, avoiding jank as the inner RecyclerView is scrolled into the viewport.</p>
   1265      *
   1266      * <p>For example, take a vertically scrolling RecyclerView with horizontally scrolling inner
   1267      * RecyclerViews. The rows always have 4 items visible in them (or 5 if not aligned). Passing
   1268      * <code>4</code> to this method for each inner RecyclerView's LinearLayoutManager will enable
   1269      * RecyclerView's prefetching feature to do create/bind work for 4 views within a row early,
   1270      * before it is scrolled on screen, instead of just the default 2.</p>
   1271      *
   1272      * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
   1273      * nested in another RecyclerView.</p>
   1274      *
   1275      * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
   1276      * views that will be visible in this view can incur unnecessary bind work, and an increase to
   1277      * the number of Views created and in active use.</p>
   1278      *
   1279      * @param itemCount Number of items to prefetch
   1280      *
   1281      * @see #isItemPrefetchEnabled()
   1282      * @see #getInitialPrefetchItemCount()
   1283      * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
   1284      */
   1285     public void setInitialPrefetchItemCount(int itemCount) {
   1286         mInitialPrefetchItemCount = itemCount;
   1287     }
   1288 
   1289     /**
   1290      * Gets the number of items to prefetch in
   1291      * {@link #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)}, which defines
   1292      * how many inner items should be prefetched when this LayoutManager's RecyclerView
   1293      * is nested inside another RecyclerView.
   1294      *
   1295      * @see #isItemPrefetchEnabled()
   1296      * @see #setInitialPrefetchItemCount(int)
   1297      * @see #collectInitialPrefetchPositions(int, LayoutPrefetchRegistry)
   1298      *
   1299      * @return number of items to prefetch.
   1300      */
   1301     public int getInitialPrefetchItemCount() {
   1302         return mInitialPrefetchItemCount;
   1303     }
   1304 
   1305     @Override
   1306     public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
   1307             LayoutPrefetchRegistry layoutPrefetchRegistry) {
   1308         int delta = (mOrientation == HORIZONTAL) ? dx : dy;
   1309         if (getChildCount() == 0 || delta == 0) {
   1310             // can't support this scroll, so don't bother prefetching
   1311             return;
   1312         }
   1313 
   1314         ensureLayoutState();
   1315         final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
   1316         final int absDy = Math.abs(delta);
   1317         updateLayoutState(layoutDirection, absDy, true, state);
   1318         collectPrefetchPositionsForLayoutState(state, mLayoutState, layoutPrefetchRegistry);
   1319     }
   1320 
   1321     int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
   1322         if (getChildCount() == 0 || dy == 0) {
   1323             return 0;
   1324         }
   1325         mLayoutState.mRecycle = true;
   1326         ensureLayoutState();
   1327         final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
   1328         final int absDy = Math.abs(dy);
   1329         updateLayoutState(layoutDirection, absDy, true, state);
   1330         final int consumed = mLayoutState.mScrollingOffset
   1331                 + fill(recycler, mLayoutState, state, false);
   1332         if (consumed < 0) {
   1333             if (DEBUG) {
   1334                 Log.d(TAG, "Don't have any more elements to scroll");
   1335             }
   1336             return 0;
   1337         }
   1338         final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
   1339         mOrientationHelper.offsetChildren(-scrolled);
   1340         if (DEBUG) {
   1341             Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
   1342         }
   1343         mLayoutState.mLastScrollDelta = scrolled;
   1344         return scrolled;
   1345     }
   1346 
   1347     @Override
   1348     public void assertNotInLayoutOrScroll(String message) {
   1349         if (mPendingSavedState == null) {
   1350             super.assertNotInLayoutOrScroll(message);
   1351         }
   1352     }
   1353 
   1354     /**
   1355      * Recycles children between given indices.
   1356      *
   1357      * @param startIndex inclusive
   1358      * @param endIndex   exclusive
   1359      */
   1360     private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
   1361         if (startIndex == endIndex) {
   1362             return;
   1363         }
   1364         if (DEBUG) {
   1365             Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
   1366         }
   1367         if (endIndex > startIndex) {
   1368             for (int i = endIndex - 1; i >= startIndex; i--) {
   1369                 removeAndRecycleViewAt(i, recycler);
   1370             }
   1371         } else {
   1372             for (int i = startIndex; i > endIndex; i--) {
   1373                 removeAndRecycleViewAt(i, recycler);
   1374             }
   1375         }
   1376     }
   1377 
   1378     /**
   1379      * Recycles views that went out of bounds after scrolling towards the end of the layout.
   1380      * <p>
   1381      * Checks both layout position and visible position to guarantee that the view is not visible.
   1382      *
   1383      * @param recycler Recycler instance of {@link RecyclerView}
   1384      * @param dt       This can be used to add additional padding to the visible area. This is used
   1385      *                 to detect children that will go out of bounds after scrolling, without
   1386      *                 actually moving them.
   1387      */
   1388     private void recycleViewsFromStart(RecyclerView.Recycler recycler, int dt) {
   1389         if (dt < 0) {
   1390             if (DEBUG) {
   1391                 Log.d(TAG, "Called recycle from start with a negative value. This might happen"
   1392                         + " during layout changes but may be sign of a bug");
   1393             }
   1394             return;
   1395         }
   1396         // ignore padding, ViewGroup may not clip children.
   1397         final int limit = dt;
   1398         final int childCount = getChildCount();
   1399         if (mShouldReverseLayout) {
   1400             for (int i = childCount - 1; i >= 0; i--) {
   1401                 View child = getChildAt(i);
   1402                 if (mOrientationHelper.getDecoratedEnd(child) > limit
   1403                         || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
   1404                     // stop here
   1405                     recycleChildren(recycler, childCount - 1, i);
   1406                     return;
   1407                 }
   1408             }
   1409         } else {
   1410             for (int i = 0; i < childCount; i++) {
   1411                 View child = getChildAt(i);
   1412                 if (mOrientationHelper.getDecoratedEnd(child) > limit
   1413                         || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
   1414                     // stop here
   1415                     recycleChildren(recycler, 0, i);
   1416                     return;
   1417                 }
   1418             }
   1419         }
   1420     }
   1421 
   1422 
   1423     /**
   1424      * Recycles views that went out of bounds after scrolling towards the start of the layout.
   1425      * <p>
   1426      * Checks both layout position and visible position to guarantee that the view is not visible.
   1427      *
   1428      * @param recycler Recycler instance of {@link RecyclerView}
   1429      * @param dt       This can be used to add additional padding to the visible area. This is used
   1430      *                 to detect children that will go out of bounds after scrolling, without
   1431      *                 actually moving them.
   1432      */
   1433     private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
   1434         final int childCount = getChildCount();
   1435         if (dt < 0) {
   1436             if (DEBUG) {
   1437                 Log.d(TAG, "Called recycle from end with a negative value. This might happen"
   1438                         + " during layout changes but may be sign of a bug");
   1439             }
   1440             return;
   1441         }
   1442         final int limit = mOrientationHelper.getEnd() - dt;
   1443         if (mShouldReverseLayout) {
   1444             for (int i = 0; i < childCount; i++) {
   1445                 View child = getChildAt(i);
   1446                 if (mOrientationHelper.getDecoratedStart(child) < limit
   1447                         || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
   1448                     // stop here
   1449                     recycleChildren(recycler, 0, i);
   1450                     return;
   1451                 }
   1452             }
   1453         } else {
   1454             for (int i = childCount - 1; i >= 0; i--) {
   1455                 View child = getChildAt(i);
   1456                 if (mOrientationHelper.getDecoratedStart(child) < limit
   1457                         || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
   1458                     // stop here
   1459                     recycleChildren(recycler, childCount - 1, i);
   1460                     return;
   1461                 }
   1462             }
   1463         }
   1464     }
   1465 
   1466     /**
   1467      * Helper method to call appropriate recycle method depending on current layout direction
   1468      *
   1469      * @param recycler    Current recycler that is attached to RecyclerView
   1470      * @param layoutState Current layout state. Right now, this object does not change but
   1471      *                    we may consider moving it out of this view so passing around as a
   1472      *                    parameter for now, rather than accessing {@link #mLayoutState}
   1473      * @see #recycleViewsFromStart(RecyclerView.Recycler, int)
   1474      * @see #recycleViewsFromEnd(RecyclerView.Recycler, int)
   1475      * @see LinearLayoutManager.LayoutState#mLayoutDirection
   1476      */
   1477     private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
   1478         if (!layoutState.mRecycle || layoutState.mInfinite) {
   1479             return;
   1480         }
   1481         if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
   1482             recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
   1483         } else {
   1484             recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
   1485         }
   1486     }
   1487 
   1488     /**
   1489      * The magic functions :). Fills the given layout, defined by the layoutState. This is fairly
   1490      * independent from the rest of the {@link LinearLayoutManager}
   1491      * and with little change, can be made publicly available as a helper class.
   1492      *
   1493      * @param recycler        Current recycler that is attached to RecyclerView
   1494      * @param layoutState     Configuration on how we should fill out the available space.
   1495      * @param state           Context passed by the RecyclerView to control scroll steps.
   1496      * @param stopOnFocusable If true, filling stops in the first focusable new child
   1497      * @return Number of pixels that it added. Useful for scroll functions.
   1498      */
   1499     int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
   1500             RecyclerView.State state, boolean stopOnFocusable) {
   1501         // max offset we should set is mFastScroll + available
   1502         final int start = layoutState.mAvailable;
   1503         if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
   1504             // TODO ugly bug fix. should not happen
   1505             if (layoutState.mAvailable < 0) {
   1506                 layoutState.mScrollingOffset += layoutState.mAvailable;
   1507             }
   1508             recycleByLayoutState(recycler, layoutState);
   1509         }
   1510         int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
   1511         LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
   1512         while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
   1513             layoutChunkResult.resetInternal();
   1514             if (RecyclerView.VERBOSE_TRACING) {
   1515                 TraceCompat.beginSection("LLM LayoutChunk");
   1516             }
   1517             layoutChunk(recycler, state, layoutState, layoutChunkResult);
   1518             if (RecyclerView.VERBOSE_TRACING) {
   1519                 TraceCompat.endSection();
   1520             }
   1521             if (layoutChunkResult.mFinished) {
   1522                 break;
   1523             }
   1524             layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
   1525             /**
   1526              * Consume the available space if:
   1527              * * layoutChunk did not request to be ignored
   1528              * * OR we are laying out scrap children
   1529              * * OR we are not doing pre-layout
   1530              */
   1531             if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null
   1532                     || !state.isPreLayout()) {
   1533                 layoutState.mAvailable -= layoutChunkResult.mConsumed;
   1534                 // we keep a separate remaining space because mAvailable is important for recycling
   1535                 remainingSpace -= layoutChunkResult.mConsumed;
   1536             }
   1537 
   1538             if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
   1539                 layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
   1540                 if (layoutState.mAvailable < 0) {
   1541                     layoutState.mScrollingOffset += layoutState.mAvailable;
   1542                 }
   1543                 recycleByLayoutState(recycler, layoutState);
   1544             }
   1545             if (stopOnFocusable && layoutChunkResult.mFocusable) {
   1546                 break;
   1547             }
   1548         }
   1549         if (DEBUG) {
   1550             validateChildOrder();
   1551         }
   1552         return start - layoutState.mAvailable;
   1553     }
   1554 
   1555     void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
   1556             LayoutState layoutState, LayoutChunkResult result) {
   1557         View view = layoutState.next(recycler);
   1558         if (view == null) {
   1559             if (DEBUG && layoutState.mScrapList == null) {
   1560                 throw new RuntimeException("received null view when unexpected");
   1561             }
   1562             // if we are laying out views in scrap, this may return null which means there is
   1563             // no more items to layout.
   1564             result.mFinished = true;
   1565             return;
   1566         }
   1567         RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
   1568         if (layoutState.mScrapList == null) {
   1569             if (mShouldReverseLayout == (layoutState.mLayoutDirection
   1570                     == LayoutState.LAYOUT_START)) {
   1571                 addView(view);
   1572             } else {
   1573                 addView(view, 0);
   1574             }
   1575         } else {
   1576             if (mShouldReverseLayout == (layoutState.mLayoutDirection
   1577                     == LayoutState.LAYOUT_START)) {
   1578                 addDisappearingView(view);
   1579             } else {
   1580                 addDisappearingView(view, 0);
   1581             }
   1582         }
   1583         measureChildWithMargins(view, 0, 0);
   1584         result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
   1585         int left, top, right, bottom;
   1586         if (mOrientation == VERTICAL) {
   1587             if (isLayoutRTL()) {
   1588                 right = getWidth() - getPaddingRight();
   1589                 left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
   1590             } else {
   1591                 left = getPaddingLeft();
   1592                 right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
   1593             }
   1594             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
   1595                 bottom = layoutState.mOffset;
   1596                 top = layoutState.mOffset - result.mConsumed;
   1597             } else {
   1598                 top = layoutState.mOffset;
   1599                 bottom = layoutState.mOffset + result.mConsumed;
   1600             }
   1601         } else {
   1602             top = getPaddingTop();
   1603             bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
   1604 
   1605             if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
   1606                 right = layoutState.mOffset;
   1607                 left = layoutState.mOffset - result.mConsumed;
   1608             } else {
   1609                 left = layoutState.mOffset;
   1610                 right = layoutState.mOffset + result.mConsumed;
   1611             }
   1612         }
   1613         // We calculate everything with View's bounding box (which includes decor and margins)
   1614         // To calculate correct layout position, we subtract margins.
   1615         layoutDecoratedWithMargins(view, left, top, right, bottom);
   1616         if (DEBUG) {
   1617             Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
   1618                     + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
   1619                     + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
   1620         }
   1621         // Consume the available space if the view is not removed OR changed
   1622         if (params.isItemRemoved() || params.isItemChanged()) {
   1623             result.mIgnoreConsumed = true;
   1624         }
   1625         result.mFocusable = view.hasFocusable();
   1626     }
   1627 
   1628     @Override
   1629     boolean shouldMeasureTwice() {
   1630         return getHeightMode() != View.MeasureSpec.EXACTLY
   1631                 && getWidthMode() != View.MeasureSpec.EXACTLY
   1632                 && hasFlexibleChildInBothOrientations();
   1633     }
   1634 
   1635     /**
   1636      * Converts a focusDirection to orientation.
   1637      *
   1638      * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
   1639      *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
   1640      *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
   1641      *                       or 0 for not applicable
   1642      * @return {@link LayoutState#LAYOUT_START} or {@link LayoutState#LAYOUT_END} if focus direction
   1643      * is applicable to current state, {@link LayoutState#INVALID_LAYOUT} otherwise.
   1644      */
   1645     int convertFocusDirectionToLayoutDirection(int focusDirection) {
   1646         switch (focusDirection) {
   1647             case View.FOCUS_BACKWARD:
   1648                 if (mOrientation == VERTICAL) {
   1649                     return LayoutState.LAYOUT_START;
   1650                 } else if (isLayoutRTL()) {
   1651                     return LayoutState.LAYOUT_END;
   1652                 } else {
   1653                     return LayoutState.LAYOUT_START;
   1654                 }
   1655             case View.FOCUS_FORWARD:
   1656                 if (mOrientation == VERTICAL) {
   1657                     return LayoutState.LAYOUT_END;
   1658                 } else if (isLayoutRTL()) {
   1659                     return LayoutState.LAYOUT_START;
   1660                 } else {
   1661                     return LayoutState.LAYOUT_END;
   1662                 }
   1663             case View.FOCUS_UP:
   1664                 return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
   1665                         : LayoutState.INVALID_LAYOUT;
   1666             case View.FOCUS_DOWN:
   1667                 return mOrientation == VERTICAL ? LayoutState.LAYOUT_END
   1668                         : LayoutState.INVALID_LAYOUT;
   1669             case View.FOCUS_LEFT:
   1670                 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_START
   1671                         : LayoutState.INVALID_LAYOUT;
   1672             case View.FOCUS_RIGHT:
   1673                 return mOrientation == HORIZONTAL ? LayoutState.LAYOUT_END
   1674                         : LayoutState.INVALID_LAYOUT;
   1675             default:
   1676                 if (DEBUG) {
   1677                     Log.d(TAG, "Unknown focus request:" + focusDirection);
   1678                 }
   1679                 return LayoutState.INVALID_LAYOUT;
   1680         }
   1681 
   1682     }
   1683 
   1684     /**
   1685      * Convenience method to find the child closes to start. Caller should check it has enough
   1686      * children.
   1687      *
   1688      * @return The child closes to start of the layout from user's perspective.
   1689      */
   1690     private View getChildClosestToStart() {
   1691         return getChildAt(mShouldReverseLayout ? getChildCount() - 1 : 0);
   1692     }
   1693 
   1694     /**
   1695      * Convenience method to find the child closes to end. Caller should check it has enough
   1696      * children.
   1697      *
   1698      * @return The child closes to end of the layout from user's perspective.
   1699      */
   1700     private View getChildClosestToEnd() {
   1701         return getChildAt(mShouldReverseLayout ? 0 : getChildCount() - 1);
   1702     }
   1703 
   1704     /**
   1705      * Convenience method to find the visible child closes to start. Caller should check if it has
   1706      * enough children.
   1707      *
   1708      * @param completelyVisible Whether child should be completely visible or not
   1709      * @return The first visible child closest to start of the layout from user's perspective.
   1710      */
   1711     private View findFirstVisibleChildClosestToStart(boolean completelyVisible,
   1712             boolean acceptPartiallyVisible) {
   1713         if (mShouldReverseLayout) {
   1714             return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
   1715                     acceptPartiallyVisible);
   1716         } else {
   1717             return findOneVisibleChild(0, getChildCount(), completelyVisible,
   1718                     acceptPartiallyVisible);
   1719         }
   1720     }
   1721 
   1722     /**
   1723      * Convenience method to find the visible child closes to end. Caller should check if it has
   1724      * enough children.
   1725      *
   1726      * @param completelyVisible Whether child should be completely visible or not
   1727      * @return The first visible child closest to end of the layout from user's perspective.
   1728      */
   1729     private View findFirstVisibleChildClosestToEnd(boolean completelyVisible,
   1730             boolean acceptPartiallyVisible) {
   1731         if (mShouldReverseLayout) {
   1732             return findOneVisibleChild(0, getChildCount(), completelyVisible,
   1733                     acceptPartiallyVisible);
   1734         } else {
   1735             return findOneVisibleChild(getChildCount() - 1, -1, completelyVisible,
   1736                     acceptPartiallyVisible);
   1737         }
   1738     }
   1739 
   1740 
   1741     /**
   1742      * Among the children that are suitable to be considered as an anchor child, returns the one
   1743      * closest to the end of the layout.
   1744      * <p>
   1745      * Due to ambiguous adapter updates or children being removed, some children's positions may be
   1746      * invalid. This method is a best effort to find a position within adapter bounds if possible.
   1747      * <p>
   1748      * It also prioritizes children that are within the visible bounds.
   1749      * @return A View that can be used an an anchor View.
   1750      */
   1751     private View findReferenceChildClosestToEnd(RecyclerView.Recycler recycler,
   1752             RecyclerView.State state) {
   1753         return mShouldReverseLayout ? findFirstReferenceChild(recycler, state) :
   1754                 findLastReferenceChild(recycler, state);
   1755     }
   1756 
   1757     /**
   1758      * Among the children that are suitable to be considered as an anchor child, returns the one
   1759      * closest to the start of the layout.
   1760      * <p>
   1761      * Due to ambiguous adapter updates or children being removed, some children's positions may be
   1762      * invalid. This method is a best effort to find a position within adapter bounds if possible.
   1763      * <p>
   1764      * It also prioritizes children that are within the visible bounds.
   1765      *
   1766      * @return A View that can be used an an anchor View.
   1767      */
   1768     private View findReferenceChildClosestToStart(RecyclerView.Recycler recycler,
   1769             RecyclerView.State state) {
   1770         return mShouldReverseLayout ? findLastReferenceChild(recycler, state) :
   1771                 findFirstReferenceChild(recycler, state);
   1772     }
   1773 
   1774     private View findFirstReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1775         return findReferenceChild(recycler, state, 0, getChildCount(), state.getItemCount());
   1776     }
   1777 
   1778     private View findLastReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
   1779         return findReferenceChild(recycler, state, getChildCount() - 1, -1, state.getItemCount());
   1780     }
   1781 
   1782     // overridden by GridLayoutManager
   1783     View findReferenceChild(RecyclerView.Recycler recycler, RecyclerView.State state,
   1784             int start, int end, int itemCount) {
   1785         ensureLayoutState();
   1786         View invalidMatch = null;
   1787         View outOfBoundsMatch = null;
   1788         final int boundsStart = mOrientationHelper.getStartAfterPadding();
   1789         final int boundsEnd = mOrientationHelper.getEndAfterPadding();
   1790         final int diff = end > start ? 1 : -1;
   1791         for (int i = start; i != end; i += diff) {
   1792             final View view = getChildAt(i);
   1793             final int position = getPosition(view);
   1794             if (position >= 0 && position < itemCount) {
   1795                 if (((RecyclerView.LayoutParams) view.getLayoutParams()).isItemRemoved()) {
   1796                     if (invalidMatch == null) {
   1797                         invalidMatch = view; // removed item, least preferred
   1798                     }
   1799                 } else if (mOrientationHelper.getDecoratedStart(view) >= boundsEnd
   1800                         || mOrientationHelper.getDecoratedEnd(view) < boundsStart) {
   1801                     if (outOfBoundsMatch == null) {
   1802                         outOfBoundsMatch = view; // item is not visible, less preferred
   1803                     }
   1804                 } else {
   1805                     return view;
   1806                 }
   1807             }
   1808         }
   1809         return outOfBoundsMatch != null ? outOfBoundsMatch : invalidMatch;
   1810     }
   1811 
   1812     // returns the out-of-bound child view closest to RV's end bounds. An out-of-bound child is
   1813     // defined as a child that's either partially or fully invisible (outside RV's padding area).
   1814     private View findPartiallyOrCompletelyInvisibleChildClosestToEnd(RecyclerView.Recycler recycler,
   1815             RecyclerView.State state) {
   1816         return mShouldReverseLayout ? findFirstPartiallyOrCompletelyInvisibleChild(recycler, state)
   1817                 : findLastPartiallyOrCompletelyInvisibleChild(recycler, state);
   1818     }
   1819 
   1820     // returns the out-of-bound child view closest to RV's starting bounds. An out-of-bound child is
   1821     // defined as a child that's either partially or fully invisible (outside RV's padding area).
   1822     private View findPartiallyOrCompletelyInvisibleChildClosestToStart(
   1823             RecyclerView.Recycler recycler, RecyclerView.State state) {
   1824         return mShouldReverseLayout ? findLastPartiallyOrCompletelyInvisibleChild(recycler, state) :
   1825                 findFirstPartiallyOrCompletelyInvisibleChild(recycler, state);
   1826     }
   1827 
   1828     private View findFirstPartiallyOrCompletelyInvisibleChild(RecyclerView.Recycler recycler,
   1829             RecyclerView.State state) {
   1830         return findOnePartiallyOrCompletelyInvisibleChild(0, getChildCount());
   1831     }
   1832 
   1833     private View findLastPartiallyOrCompletelyInvisibleChild(RecyclerView.Recycler recycler,
   1834             RecyclerView.State state) {
   1835         return findOnePartiallyOrCompletelyInvisibleChild(getChildCount() - 1, -1);
   1836     }
   1837 
   1838     /**
   1839      * Returns the adapter position of the first visible view. This position does not include
   1840      * adapter changes that were dispatched after the last layout pass.
   1841      * <p>
   1842      * Note that, this value is not affected by layout orientation or item order traversal.
   1843      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
   1844      * not in the layout.
   1845      * <p>
   1846      * If RecyclerView has item decorators, they will be considered in calculations as well.
   1847      * <p>
   1848      * LayoutManager may pre-cache some views that are not necessarily visible. Those views
   1849      * are ignored in this method.
   1850      *
   1851      * @return The adapter position of the first visible item or {@link RecyclerView#NO_POSITION} if
   1852      * there aren't any visible items.
   1853      * @see #findFirstCompletelyVisibleItemPosition()
   1854      * @see #findLastVisibleItemPosition()
   1855      */
   1856     public int findFirstVisibleItemPosition() {
   1857         final View child = findOneVisibleChild(0, getChildCount(), false, true);
   1858         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
   1859     }
   1860 
   1861     /**
   1862      * Returns the adapter position of the first fully visible view. This position does not include
   1863      * adapter changes that were dispatched after the last layout pass.
   1864      * <p>
   1865      * Note that bounds check is only performed in the current orientation. That means, if
   1866      * LayoutManager is horizontal, it will only check the view's left and right edges.
   1867      *
   1868      * @return The adapter position of the first fully visible item or
   1869      * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
   1870      * @see #findFirstVisibleItemPosition()
   1871      * @see #findLastCompletelyVisibleItemPosition()
   1872      */
   1873     public int findFirstCompletelyVisibleItemPosition() {
   1874         final View child = findOneVisibleChild(0, getChildCount(), true, false);
   1875         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
   1876     }
   1877 
   1878     /**
   1879      * Returns the adapter position of the last visible view. This position does not include
   1880      * adapter changes that were dispatched after the last layout pass.
   1881      * <p>
   1882      * Note that, this value is not affected by layout orientation or item order traversal.
   1883      * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
   1884      * not in the layout.
   1885      * <p>
   1886      * If RecyclerView has item decorators, they will be considered in calculations as well.
   1887      * <p>
   1888      * LayoutManager may pre-cache some views that are not necessarily visible. Those views
   1889      * are ignored in this method.
   1890      *
   1891      * @return The adapter position of the last visible view or {@link RecyclerView#NO_POSITION} if
   1892      * there aren't any visible items.
   1893      * @see #findLastCompletelyVisibleItemPosition()
   1894      * @see #findFirstVisibleItemPosition()
   1895      */
   1896     public int findLastVisibleItemPosition() {
   1897         final View child = findOneVisibleChild(getChildCount() - 1, -1, false, true);
   1898         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
   1899     }
   1900 
   1901     /**
   1902      * Returns the adapter position of the last fully visible view. This position does not include
   1903      * adapter changes that were dispatched after the last layout pass.
   1904      * <p>
   1905      * Note that bounds check is only performed in the current orientation. That means, if
   1906      * LayoutManager is horizontal, it will only check the view's left and right edges.
   1907      *
   1908      * @return The adapter position of the last fully visible view or
   1909      * {@link RecyclerView#NO_POSITION} if there aren't any visible items.
   1910      * @see #findLastVisibleItemPosition()
   1911      * @see #findFirstCompletelyVisibleItemPosition()
   1912      */
   1913     public int findLastCompletelyVisibleItemPosition() {
   1914         final View child = findOneVisibleChild(getChildCount() - 1, -1, true, false);
   1915         return child == null ? RecyclerView.NO_POSITION : getPosition(child);
   1916     }
   1917 
   1918     // Returns the first child that is visible in the provided index range, i.e. either partially or
   1919     // fully visible depending on the arguments provided. Completely invisible children are not
   1920     // acceptable by this method, but could be returned
   1921     // using #findOnePartiallyOrCompletelyInvisibleChild
   1922     View findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible,
   1923             boolean acceptPartiallyVisible) {
   1924         ensureLayoutState();
   1925         @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0;
   1926         @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
   1927         if (completelyVisible) {
   1928             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_GT_PVS | ViewBoundsCheck.FLAG_CVS_EQ_PVS
   1929                     | ViewBoundsCheck.FLAG_CVE_LT_PVE | ViewBoundsCheck.FLAG_CVE_EQ_PVE);
   1930         } else {
   1931             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
   1932                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
   1933         }
   1934         if (acceptPartiallyVisible) {
   1935             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVE
   1936                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
   1937         }
   1938         return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck
   1939                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
   1940                         acceptableBoundsFlag) : mVerticalBoundCheck
   1941                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
   1942                         acceptableBoundsFlag);
   1943     }
   1944 
   1945     View findOnePartiallyOrCompletelyInvisibleChild(int fromIndex, int toIndex) {
   1946         ensureLayoutState();
   1947         final int next = toIndex > fromIndex ? 1 : (toIndex < fromIndex ? -1 : 0);
   1948         if (next == 0) {
   1949             return getChildAt(fromIndex);
   1950         }
   1951         @ViewBoundsCheck.ViewBounds int preferredBoundsFlag = 0;
   1952         @ViewBoundsCheck.ViewBounds int acceptableBoundsFlag = 0;
   1953         if (mOrientationHelper.getDecoratedStart(getChildAt(fromIndex))
   1954                 < mOrientationHelper.getStartAfterPadding()) {
   1955             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS | ViewBoundsCheck.FLAG_CVE_LT_PVE
   1956                     | ViewBoundsCheck.FLAG_CVE_GT_PVS);
   1957             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVS_LT_PVS
   1958                     | ViewBoundsCheck.FLAG_CVE_LT_PVE);
   1959         } else {
   1960             preferredBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE | ViewBoundsCheck.FLAG_CVS_GT_PVS
   1961                     | ViewBoundsCheck.FLAG_CVS_LT_PVE);
   1962             acceptableBoundsFlag = (ViewBoundsCheck.FLAG_CVE_GT_PVE
   1963                     | ViewBoundsCheck.FLAG_CVS_GT_PVS);
   1964         }
   1965         return (mOrientation == HORIZONTAL) ? mHorizontalBoundCheck
   1966                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
   1967                         acceptableBoundsFlag) : mVerticalBoundCheck
   1968                 .findOneViewWithinBoundFlags(fromIndex, toIndex, preferredBoundsFlag,
   1969                         acceptableBoundsFlag);
   1970     }
   1971 
   1972     @Override
   1973     public View onFocusSearchFailed(View focused, int focusDirection,
   1974             RecyclerView.Recycler recycler, RecyclerView.State state) {
   1975         resolveShouldLayoutReverse();
   1976         if (getChildCount() == 0) {
   1977             return null;
   1978         }
   1979 
   1980         final int layoutDir = convertFocusDirectionToLayoutDirection(focusDirection);
   1981         if (layoutDir == LayoutState.INVALID_LAYOUT) {
   1982             return null;
   1983         }
   1984         ensureLayoutState();
   1985         ensureLayoutState();
   1986         final int maxScroll = (int) (MAX_SCROLL_FACTOR * mOrientationHelper.getTotalSpace());
   1987         updateLayoutState(layoutDir, maxScroll, false, state);
   1988         mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
   1989         mLayoutState.mRecycle = false;
   1990         fill(recycler, mLayoutState, state, true);
   1991 
   1992         // nextCandidate is the first child view in the layout direction that's partially
   1993         // within RV's bounds, i.e. part of it is visible or it's completely invisible but still
   1994         // touching RV's bounds. This will be the unfocusable candidate view to become visible onto
   1995         // the screen if no focusable views are found in the given layout direction.
   1996         final View nextCandidate;
   1997         if (layoutDir == LayoutState.LAYOUT_START) {
   1998             nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToStart(recycler, state);
   1999         } else {
   2000             nextCandidate = findPartiallyOrCompletelyInvisibleChildClosestToEnd(recycler, state);
   2001         }
   2002         // nextFocus is meaningful only if it refers to a focusable child, in which case it
   2003         // indicates the next view to gain focus.
   2004         final View nextFocus;
   2005         if (layoutDir == LayoutState.LAYOUT_START) {
   2006             nextFocus = getChildClosestToStart();
   2007         } else {
   2008             nextFocus = getChildClosestToEnd();
   2009         }
   2010         if (nextFocus.hasFocusable()) {
   2011             if (nextCandidate == null) {
   2012                 return null;
   2013             }
   2014             return nextFocus;
   2015         }
   2016         return nextCandidate;
   2017     }
   2018 
   2019     /**
   2020      * Used for debugging.
   2021      * Logs the internal representation of children to default logger.
   2022      */
   2023     private void logChildren() {
   2024         Log.d(TAG, "internal representation of views on the screen");
   2025         for (int i = 0; i < getChildCount(); i++) {
   2026             View child = getChildAt(i);
   2027             Log.d(TAG, "item " + getPosition(child) + ", coord:"
   2028                     + mOrientationHelper.getDecoratedStart(child));
   2029         }
   2030         Log.d(TAG, "==============");
   2031     }
   2032 
   2033     /**
   2034      * Used for debugging.
   2035      * Validates that child views are laid out in correct order. This is important because rest of
   2036      * the algorithm relies on this constraint.
   2037      *
   2038      * In default layout, child 0 should be closest to screen position 0 and last child should be
   2039      * closest to position WIDTH or HEIGHT.
   2040      * In reverse layout, last child should be closes to screen position 0 and first child should
   2041      * be closest to position WIDTH  or HEIGHT
   2042      */
   2043     void validateChildOrder() {
   2044         Log.d(TAG, "validating child count " + getChildCount());
   2045         if (getChildCount() < 1) {
   2046             return;
   2047         }
   2048         int lastPos = getPosition(getChildAt(0));
   2049         int lastScreenLoc = mOrientationHelper.getDecoratedStart(getChildAt(0));
   2050         if (mShouldReverseLayout) {
   2051             for (int i = 1; i < getChildCount(); i++) {
   2052                 View child = getChildAt(i);
   2053                 int pos = getPosition(child);
   2054                 int screenLoc = mOrientationHelper.getDecoratedStart(child);
   2055                 if (pos < lastPos) {
   2056                     logChildren();
   2057                     throw new RuntimeException("detected invalid position. loc invalid? "
   2058                             + (screenLoc < lastScreenLoc));
   2059                 }
   2060                 if (screenLoc > lastScreenLoc) {
   2061                     logChildren();
   2062                     throw new RuntimeException("detected invalid location");
   2063                 }
   2064             }
   2065         } else {
   2066             for (int i = 1; i < getChildCount(); i++) {
   2067                 View child = getChildAt(i);
   2068                 int pos = getPosition(child);
   2069                 int screenLoc = mOrientationHelper.getDecoratedStart(child);
   2070                 if (pos < lastPos) {
   2071                     logChildren();
   2072                     throw new RuntimeException("detected invalid position. loc invalid? "
   2073                             + (screenLoc < lastScreenLoc));
   2074                 }
   2075                 if (screenLoc < lastScreenLoc) {
   2076                     logChildren();
   2077                     throw new RuntimeException("detected invalid location");
   2078                 }
   2079             }
   2080         }
   2081     }
   2082 
   2083     @Override
   2084     public boolean supportsPredictiveItemAnimations() {
   2085         return mPendingSavedState == null && mLastStackFromEnd == mStackFromEnd;
   2086     }
   2087 
   2088     /**
   2089      * @hide This method should be called by ItemTouchHelper only.
   2090      */
   2091     @RestrictTo(LIBRARY_GROUP)
   2092     @Override
   2093     public void prepareForDrop(@NonNull View view, @NonNull View target, int x, int y) {
   2094         assertNotInLayoutOrScroll("Cannot drop a view during a scroll or layout calculation");
   2095         ensureLayoutState();
   2096         resolveShouldLayoutReverse();
   2097         final int myPos = getPosition(view);
   2098         final int targetPos = getPosition(target);
   2099         final int dropDirection = myPos < targetPos ? LayoutState.ITEM_DIRECTION_TAIL
   2100                 : LayoutState.ITEM_DIRECTION_HEAD;
   2101         if (mShouldReverseLayout) {
   2102             if (dropDirection == LayoutState.ITEM_DIRECTION_TAIL) {
   2103                 scrollToPositionWithOffset(targetPos,
   2104                         mOrientationHelper.getEndAfterPadding()
   2105                                 - (mOrientationHelper.getDecoratedStart(target)
   2106                                 + mOrientationHelper.getDecoratedMeasurement(view)));
   2107             } else {
   2108                 scrollToPositionWithOffset(targetPos,
   2109                         mOrientationHelper.getEndAfterPadding()
   2110                                 - mOrientationHelper.getDecoratedEnd(target));
   2111             }
   2112         } else {
   2113             if (dropDirection == LayoutState.ITEM_DIRECTION_HEAD) {
   2114                 scrollToPositionWithOffset(targetPos, mOrientationHelper.getDecoratedStart(target));
   2115             } else {
   2116                 scrollToPositionWithOffset(targetPos,
   2117                         mOrientationHelper.getDecoratedEnd(target)
   2118                                 - mOrientationHelper.getDecoratedMeasurement(view));
   2119             }
   2120         }
   2121     }
   2122 
   2123     /**
   2124      * Helper class that keeps temporary state while {LayoutManager} is filling out the empty
   2125      * space.
   2126      */
   2127     static class LayoutState {
   2128 
   2129         static final String TAG = "LLM#LayoutState";
   2130 
   2131         static final int LAYOUT_START = -1;
   2132 
   2133         static final int LAYOUT_END = 1;
   2134 
   2135         static final int INVALID_LAYOUT = Integer.MIN_VALUE;
   2136 
   2137         static final int ITEM_DIRECTION_HEAD = -1;
   2138 
   2139         static final int ITEM_DIRECTION_TAIL = 1;
   2140 
   2141         static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;
   2142 
   2143         /**
   2144          * We may not want to recycle children in some cases (e.g. layout)
   2145          */
   2146         boolean mRecycle = true;
   2147 
   2148         /**
   2149          * Pixel offset where layout should start
   2150          */
   2151         int mOffset;
   2152 
   2153         /**
   2154          * Number of pixels that we should fill, in the layout direction.
   2155          */
   2156         int mAvailable;
   2157 
   2158         /**
   2159          * Current position on the adapter to get the next item.
   2160          */
   2161         int mCurrentPosition;
   2162 
   2163         /**
   2164          * Defines the direction in which the data adapter is traversed.
   2165          * Should be {@link #ITEM_DIRECTION_HEAD} or {@link #ITEM_DIRECTION_TAIL}
   2166          */
   2167         int mItemDirection;
   2168 
   2169         /**
   2170          * Defines the direction in which the layout is filled.
   2171          * Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
   2172          */
   2173         int mLayoutDirection;
   2174 
   2175         /**
   2176          * Used when LayoutState is constructed in a scrolling state.
   2177          * It should be set the amount of scrolling we can make without creating a new view.
   2178          * Settings this is required for efficient view recycling.
   2179          */
   2180         int mScrollingOffset;
   2181 
   2182         /**
   2183          * Used if you want to pre-layout items that are not yet visible.
   2184          * The difference with {@link #mAvailable} is that, when recycling, distance laid out for
   2185          * {@link #mExtra} is not considered to avoid recycling visible children.
   2186          */
   2187         int mExtra = 0;
   2188 
   2189         /**
   2190          * Equal to {@link RecyclerView.State#isPreLayout()}. When consuming scrap, if this value
   2191          * is set to true, we skip removed views since they should not be laid out in post layout
   2192          * step.
   2193          */
   2194         boolean mIsPreLayout = false;
   2195 
   2196         /**
   2197          * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
   2198          * amount.
   2199          */
   2200         int mLastScrollDelta;
   2201 
   2202         /**
   2203          * When LLM needs to layout particular views, it sets this list in which case, LayoutState
   2204          * will only return views from this list and return null if it cannot find an item.
   2205          */
   2206         List<RecyclerView.ViewHolder> mScrapList = null;
   2207 
   2208         /**
   2209          * Used when there is no limit in how many views can be laid out.
   2210          */
   2211         boolean mInfinite;
   2212 
   2213         /**
   2214          * @return true if there are more items in the data adapter
   2215          */
   2216         boolean hasMore(RecyclerView.State state) {
   2217             return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
   2218         }
   2219 
   2220         /**
   2221          * Gets the view for the next element that we should layout.
   2222          * Also updates current item index to the next item, based on {@link #mItemDirection}
   2223          *
   2224          * @return The next element that we should layout.
   2225          */
   2226         View next(RecyclerView.Recycler recycler) {
   2227             if (mScrapList != null) {
   2228                 return nextViewFromScrapList();
   2229             }
   2230             final View view = recycler.getViewForPosition(mCurrentPosition);
   2231             mCurrentPosition += mItemDirection;
   2232             return view;
   2233         }
   2234 
   2235         /**
   2236          * Returns the next item from the scrap list.
   2237          * <p>
   2238          * Upon finding a valid VH, sets current item position to VH.itemPosition + mItemDirection
   2239          *
   2240          * @return View if an item in the current position or direction exists if not null.
   2241          */
   2242         private View nextViewFromScrapList() {
   2243             final int size = mScrapList.size();
   2244             for (int i = 0; i < size; i++) {
   2245                 final View view = mScrapList.get(i).itemView;
   2246                 final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
   2247                 if (lp.isItemRemoved()) {
   2248                     continue;
   2249                 }
   2250                 if (mCurrentPosition == lp.getViewLayoutPosition()) {
   2251                     assignPositionFromScrapList(view);
   2252                     return view;
   2253                 }
   2254             }
   2255             return null;
   2256         }
   2257 
   2258         public void assignPositionFromScrapList() {
   2259             assignPositionFromScrapList(null);
   2260         }
   2261 
   2262         public void assignPositionFromScrapList(View ignore) {
   2263             final View closest = nextViewInLimitedList(ignore);
   2264             if (closest == null) {
   2265                 mCurrentPosition = RecyclerView.NO_POSITION;
   2266             } else {
   2267                 mCurrentPosition = ((RecyclerView.LayoutParams) closest.getLayoutParams())
   2268                         .getViewLayoutPosition();
   2269             }
   2270         }
   2271 
   2272         public View nextViewInLimitedList(View ignore) {
   2273             int size = mScrapList.size();
   2274             View closest = null;
   2275             int closestDistance = Integer.MAX_VALUE;
   2276             if (DEBUG && mIsPreLayout) {
   2277                 throw new IllegalStateException("Scrap list cannot be used in pre layout");
   2278             }
   2279             for (int i = 0; i < size; i++) {
   2280                 View view = mScrapList.get(i).itemView;
   2281                 final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
   2282                 if (view == ignore || lp.isItemRemoved()) {
   2283                     continue;
   2284                 }
   2285                 final int distance = (lp.getViewLayoutPosition() - mCurrentPosition)
   2286                         * mItemDirection;
   2287                 if (distance < 0) {
   2288                     continue; // item is not in current direction
   2289                 }
   2290                 if (distance < closestDistance) {
   2291                     closest = view;
   2292                     closestDistance = distance;
   2293                     if (distance == 0) {
   2294                         break;
   2295                     }
   2296                 }
   2297             }
   2298             return closest;
   2299         }
   2300 
   2301         void log() {
   2302             Log.d(TAG, "avail:" + mAvailable + ", ind:" + mCurrentPosition + ", dir:"
   2303                     + mItemDirection + ", offset:" + mOffset + ", layoutDir:" + mLayoutDirection);
   2304         }
   2305     }
   2306 
   2307     /**
   2308      * @hide
   2309      */
   2310     @RestrictTo(LIBRARY_GROUP)
   2311     public static class SavedState implements Parcelable {
   2312 
   2313         int mAnchorPosition;
   2314 
   2315         int mAnchorOffset;
   2316 
   2317         boolean mAnchorLayoutFromEnd;
   2318 
   2319         public SavedState() {
   2320 
   2321         }
   2322 
   2323         SavedState(Parcel in) {
   2324             mAnchorPosition = in.readInt();
   2325             mAnchorOffset = in.readInt();
   2326             mAnchorLayoutFromEnd = in.readInt() == 1;
   2327         }
   2328 
   2329         public SavedState(SavedState other) {
   2330             mAnchorPosition = other.mAnchorPosition;
   2331             mAnchorOffset = other.mAnchorOffset;
   2332             mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
   2333         }
   2334 
   2335         boolean hasValidAnchor() {
   2336             return mAnchorPosition >= 0;
   2337         }
   2338 
   2339         void invalidateAnchor() {
   2340             mAnchorPosition = RecyclerView.NO_POSITION;
   2341         }
   2342 
   2343         @Override
   2344         public int describeContents() {
   2345             return 0;
   2346         }
   2347 
   2348         @Override
   2349         public void writeToParcel(Parcel dest, int flags) {
   2350             dest.writeInt(mAnchorPosition);
   2351             dest.writeInt(mAnchorOffset);
   2352             dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
   2353         }
   2354 
   2355         public static final Parcelable.Creator<SavedState> CREATOR =
   2356                 new Parcelable.Creator<SavedState>() {
   2357                     @Override
   2358                     public SavedState createFromParcel(Parcel in) {
   2359                         return new SavedState(in);
   2360                     }
   2361 
   2362                     @Override
   2363                     public SavedState[] newArray(int size) {
   2364                         return new SavedState[size];
   2365                     }
   2366                 };
   2367     }
   2368 
   2369     /**
   2370      * Simple data class to keep Anchor information
   2371      */
   2372     static class AnchorInfo {
   2373         OrientationHelper mOrientationHelper;
   2374         int mPosition;
   2375         int mCoordinate;
   2376         boolean mLayoutFromEnd;
   2377         boolean mValid;
   2378 
   2379         AnchorInfo() {
   2380             reset();
   2381         }
   2382 
   2383         void reset() {
   2384             mPosition = RecyclerView.NO_POSITION;
   2385             mCoordinate = INVALID_OFFSET;
   2386             mLayoutFromEnd = false;
   2387             mValid = false;
   2388         }
   2389 
   2390         /**
   2391          * assigns anchor coordinate from the RecyclerView's padding depending on current
   2392          * layoutFromEnd value
   2393          */
   2394         void assignCoordinateFromPadding() {
   2395             mCoordinate = mLayoutFromEnd
   2396                     ? mOrientationHelper.getEndAfterPadding()
   2397                     : mOrientationHelper.getStartAfterPadding();
   2398         }
   2399 
   2400         @Override
   2401         public String toString() {
   2402             return "AnchorInfo{"
   2403                     + "mPosition=" + mPosition
   2404                     + ", mCoordinate=" + mCoordinate
   2405                     + ", mLayoutFromEnd=" + mLayoutFromEnd
   2406                     + ", mValid=" + mValid
   2407                     + '}';
   2408         }
   2409 
   2410         boolean isViewValidAsAnchor(View child, RecyclerView.State state) {
   2411             RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
   2412             return !lp.isItemRemoved() && lp.getViewLayoutPosition() >= 0
   2413                     && lp.getViewLayoutPosition() < state.getItemCount();
   2414         }
   2415 
   2416         public void assignFromViewAndKeepVisibleRect(View child, int position) {
   2417             final int spaceChange = mOrientationHelper.getTotalSpaceChange();
   2418             if (spaceChange >= 0) {
   2419                 assignFromView(child, position);
   2420                 return;
   2421             }
   2422             mPosition = position;
   2423             if (mLayoutFromEnd) {
   2424                 final int prevLayoutEnd = mOrientationHelper.getEndAfterPadding() - spaceChange;
   2425                 final int childEnd = mOrientationHelper.getDecoratedEnd(child);
   2426                 final int previousEndMargin = prevLayoutEnd - childEnd;
   2427                 mCoordinate = mOrientationHelper.getEndAfterPadding() - previousEndMargin;
   2428                 // ensure we did not push child's top out of bounds because of this
   2429                 if (previousEndMargin > 0) { // we have room to shift bottom if necessary
   2430                     final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
   2431                     final int estimatedChildStart = mCoordinate - childSize;
   2432                     final int layoutStart = mOrientationHelper.getStartAfterPadding();
   2433                     final int previousStartMargin = mOrientationHelper.getDecoratedStart(child)
   2434                             - layoutStart;
   2435                     final int startReference = layoutStart + Math.min(previousStartMargin, 0);
   2436                     final int startMargin = estimatedChildStart - startReference;
   2437                     if (startMargin < 0) {
   2438                         // offset to make top visible but not too much
   2439                         mCoordinate += Math.min(previousEndMargin, -startMargin);
   2440                     }
   2441                 }
   2442             } else {
   2443                 final int childStart = mOrientationHelper.getDecoratedStart(child);
   2444                 final int startMargin = childStart - mOrientationHelper.getStartAfterPadding();
   2445                 mCoordinate = childStart;
   2446                 if (startMargin > 0) { // we have room to fix end as well
   2447                     final int estimatedEnd = childStart
   2448                             + mOrientationHelper.getDecoratedMeasurement(child);
   2449                     final int previousLayoutEnd = mOrientationHelper.getEndAfterPadding()
   2450                             - spaceChange;
   2451                     final int previousEndMargin = previousLayoutEnd
   2452                             - mOrientationHelper.getDecoratedEnd(child);
   2453                     final int endReference = mOrientationHelper.getEndAfterPadding()
   2454                             - Math.min(0, previousEndMargin);
   2455                     final int endMargin = endReference - estimatedEnd;
   2456                     if (endMargin < 0) {
   2457                         mCoordinate -= Math.min(startMargin, -endMargin);
   2458                     }
   2459                 }
   2460             }
   2461         }
   2462 
   2463         public void assignFromView(View child, int position) {
   2464             if (mLayoutFromEnd) {
   2465                 mCoordinate = mOrientationHelper.getDecoratedEnd(child)
   2466                         + mOrientationHelper.getTotalSpaceChange();
   2467             } else {
   2468                 mCoordinate = mOrientationHelper.getDecoratedStart(child);
   2469             }
   2470 
   2471             mPosition = position;
   2472         }
   2473     }
   2474 
   2475     protected static class LayoutChunkResult {
   2476         public int mConsumed;
   2477         public boolean mFinished;
   2478         public boolean mIgnoreConsumed;
   2479         public boolean mFocusable;
   2480 
   2481         void resetInternal() {
   2482             mConsumed = 0;
   2483             mFinished = false;
   2484             mIgnoreConsumed = false;
   2485             mFocusable = false;
   2486         }
   2487     }
   2488 }
   2489