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