content: package me.yifeiyuan.flap.widget;
  
  import android.annotation.SuppressLint;
  import android.content.Context;
  import android.graphics.PointF;
  import android.graphics.Rect;
  import android.os.Parcel;
  import android.os.Parcelable;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.accessibility.AccessibilityEvent;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.annotation.RestrictTo;
  import androidx.core.view.ViewCompat;
  import androidx.recyclerview.widget.LinearSmoothScroller;
  import androidx.recyclerview.widget.OrientationHelper;
  import androidx.recyclerview.widget.RecyclerView;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.BitSet;
  import java.util.List;
  
  import static androidx.annotation.RestrictTo.Scope.LIBRARY;
  
  /**
   * FlapIndexedStaggeredGridLayoutManager 会按照 Adapter.data 中的 index 来布局 Component，而不是完全混乱的。
   * <p>
   * 例如：
   * 0 1 2
   * 3 4 5
   * 6 7 8
   * <p>
   * 代码基于 rv 1.2.1 的 StaggeredGridLayoutManager 代码修改
   * <p>
   * Created by 程序亦非猿 on 2022/9/22.
   *
   * @see IndexedAdapterHelper
   * @see IndexedLayoutState
   * @see IndexedOpReorderer
   * @see IndexedScrollbarHelper
   * @since 3.1.5
   */
  public class FlapIndexedStaggeredGridLayoutManager extends RecyclerView.LayoutManager implements
          RecyclerView.SmoothScroller.ScrollVectorProvider {
  
      private static final String TAG = "FlapIndexedSGLM";
  
      static final boolean DEBUG = false;
  
      public static final int HORIZONTAL = RecyclerView.HORIZONTAL;
  
      public static final int VERTICAL = RecyclerView.VERTICAL;
  
      /**
       * Does not do anything to hide gaps.
       */
      public static final int GAP_HANDLING_NONE = 0;
  
      /**
       * @deprecated No longer supported.
       */
      @SuppressWarnings("unused")
      @Deprecated
      public static final int GAP_HANDLING_LAZY = 1;
  
      /**
       * When scroll state is changed to {@link RecyclerView#SCROLL_STATE_IDLE}, StaggeredGrid will
       * check if there are gaps in the because of full span items. If it finds, it will re-layout
       * and move items to correct positions with animations.
       * <p>
       * For example, if LayoutManager ends up with the following layout due to adapter changes:
       * <pre>
       * AAA
       * _BC
       * DDD
       * </pre>
       * <p>
       * It will animate to the following state:
       * <pre>
       * AAA
       * BC_
       * DDD
       * </pre>
       */
      public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2;
  
      static final int INVALID_OFFSET = Integer.MIN_VALUE;
      /**
       * While trying to find next view to focus, LayoutManager will not try to scroll more
       * than this factor times the total space of the list. If layout is vertical, total space is the
       * height minus padding, if layout is horizontal, total space is the width minus padding.
       */
      private static final float MAX_SCROLL_FACTOR = 1 / 3f;
  
      /**
       * Number of spans
       */
      private int mSpanCount = -1;
  
      FlapIndexedStaggeredGridLayoutManager.Span[] mSpans;
  
      /**
       * Primary orientation is the layout's orientation, secondary orientation is the orientation
       * for spans. Having both makes code much cleaner for calculations.
       */
      @NonNull
      OrientationHelper mPrimaryOrientation;
      @NonNull
      OrientationHelper mSecondaryOrientation;
  
      private int mOrientation;
  
      /**
       * The width or height per span, depending on the orientation.
       */
      private int mSizePerSpan;
  
      @NonNull
      private final IndexedLayoutState mLayoutState;
  
      boolean mReverseLayout = false;
  
      /**
       * Aggregated reverse layout value that takes RTL into account.
       */
      boolean mShouldReverseLayout = false;
  
      /**
       * Temporary variable used during fill method to check which spans needs to be filled.
       */
      private BitSet mRemainingSpans;
  
      /**
       * When LayoutManager needs to scroll to a position, it sets this variable and requests a
       * layout which will check this variable and re-layout accordingly.
       */
      int mPendingScrollPosition = RecyclerView.NO_POSITION;
  
      /**
       * Used to keep the offset value when {@link #scrollToPositionWithOffset(int, int)} is
       * called.
       */
      int mPendingScrollPositionOffset = INVALID_OFFSET;
  
      /**
       * Keeps the mapping between the adapter positions and spans. This is necessary to provide
       * a consistent experience when user scrolls the list.
       */
      FlapIndexedStaggeredGridLayoutManager.LazySpanLookup mLazySpanLookup = new FlapIndexedStaggeredGridLayoutManager.LazySpanLookup();
  
      /**
       * 修改：默认改为 GAP_HANDLING_NONE
       * how we handle gaps in UI.
       */
  //    private int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
      private int mGapStrategy = GAP_HANDLING_NONE;
  
      /**
       * Saved state needs this information to properly layout on restore.
       */
      private boolean mLastLayoutFromEnd;
  
      /**
       * Saved state and onLayout needs this information to re-layout properly
       */
      private boolean mLastLayoutRTL;
  
      /**
       * SavedState is not handled until a layout happens. This is where we keep it until next
       * layout.
       */
      private FlapIndexedStaggeredGridLayoutManager.SavedState mPendingSavedState;
  
      /**
       * Re-used measurement specs. updated by onLayout.
       */
      private int mFullSizeSpec;
  
      /**
       * Re-used rectangle to get child decor offsets.
       */
      private final Rect mTmpRect = new Rect();
  
      /**
       * Re-used anchor info.
       */
      private final FlapIndexedStaggeredGridLayoutManager.AnchorInfo mAnchorInfo = new FlapIndexedStaggeredGridLayoutManager.AnchorInfo();
  
      /**
       * If a full span item is invalid / or created in reverse direction; it may create gaps in
       * the UI. While laying out, if such case is detected, we set this flag.
       * <p>
       * After scrolling stops, we check this flag and if it is set, re-layout.
       */
      private boolean mLaidOutInvalidFullSpan = false;
  
      /**
       * Works the same way as {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}.
       * see {@link android.widget.AbsListView#setSmoothScrollbarEnabled(boolean)}
       */
      private boolean mSmoothScrollbarEnabled = true;
  
      /**
       * Temporary array used (solely in {@link #collectAdjacentPrefetchPositions}) for stashing and
       * sorting distances to views being prefetched.
       */
      private int[] mPrefetchDistances;
  
      private final Runnable mCheckForGapsRunnable = new Runnable() {
          @Override
          public void run() {
              checkForGaps();
          }
      };
  
      /**
       * Constructor used when layout manager is set in XML by RecyclerView attribute
       * "layoutManager". Defaults to single column and vertical.
       */
      @SuppressWarnings("unused")
      public FlapIndexedStaggeredGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
                                                   int defStyleRes) {
          Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
          setOrientation(properties.orientation);
          setSpanCount(properties.spanCount);
          setReverseLayout(properties.reverseLayout);
          mLayoutState = new IndexedLayoutState();
          createOrientationHelpers();
      }
  
      /**
       * Creates a StaggeredGridLayoutManager with given parameters.
       *
       * @param spanCount   If orientation is vertical, spanCount is number of columns. If
       *                    orientation is horizontal, spanCount is number of rows.
       * @param orientation {@link #VERTICAL} or {@link #HORIZONTAL}
       */
      public FlapIndexedStaggeredGridLayoutManager(int spanCount, int orientation) {
          mOrientation = orientation;
          setSpanCount(spanCount);
          mLayoutState = new IndexedLayoutState();
          createOrientationHelpers();
      }
  
      public FlapIndexedStaggeredGridLayoutManager(int spanCount) {
          mOrientation = RecyclerView.VERTICAL;
          setSpanCount(spanCount);
          mLayoutState = new IndexedLayoutState();
          createOrientationHelpers();
      }
  
      @Override
      public boolean isAutoMeasureEnabled() {
          return mGapStrategy != GAP_HANDLING_NONE;
      }
  
      private void createOrientationHelpers() {
          mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
          mSecondaryOrientation = OrientationHelper
                  .createOrientationHelper(this, 1 - mOrientation);
      }
  
      /**
       * Checks for gaps in the UI that may be caused by adapter changes.
       * <p>
       * When a full span item is laid out in reverse direction, it sets a flag which we check when
       * scroll is stopped (or re-layout happens) and re-layout after first valid item.
       */
      boolean checkForGaps() {
          if (getChildCount() == 0 || mGapStrategy == GAP_HANDLING_NONE || !isAttachedToWindow()) {
              return false;
          }
          final int minPos, maxPos;
          if (mShouldReverseLayout) {
              minPos = getLastChildPosition();
              maxPos = getFirstChildPosition();
          } else {
              minPos = getFirstChildPosition();
              maxPos = getLastChildPosition();
          }
          if (minPos == 0) {
              View gapView = hasGapsToFix();
              if (gapView != null) {
                  mLazySpanLookup.clear();
                  requestSimpleAnimationsInNextLayout();
                  requestLayout();
                  return true;
              }
          }
          if (!mLaidOutInvalidFullSpan) {
              return false;
          }
          int invalidGapDir = mShouldReverseLayout ? IndexedLayoutState.LAYOUT_START : IndexedLayoutState.LAYOUT_END;
          final FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem invalidFsi = mLazySpanLookup
                  .getFirstFullSpanItemInRange(minPos, maxPos + 1, invalidGapDir, true);
          if (invalidFsi == null) {
              mLaidOutInvalidFullSpan = false;
              mLazySpanLookup.forceInvalidateAfter(maxPos + 1);
              return false;
          }
          final FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem validFsi = mLazySpanLookup
                  .getFirstFullSpanItemInRange(minPos, invalidFsi.mPosition,
                          invalidGapDir * -1, true);
          if (validFsi == null) {
              mLazySpanLookup.forceInvalidateAfter(invalidFsi.mPosition);
          } else {
              mLazySpanLookup.forceInvalidateAfter(validFsi.mPosition + 1);
          }
          requestSimpleAnimationsInNextLayout();
          requestLayout();
          return true;
      }
  
      @Override
      public void onScrollStateChanged(int state) {
          try {
              if (state == RecyclerView.SCROLL_STATE_IDLE) {
                  checkForGaps();
                  if (reLayoutOnScrollToTop) {
                      handleAutoReLayoutWhenScrollToTop();
                  }
              }
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      private boolean reLayoutOnScrollToTop = false;
  
      public void setReLayoutOnScrollToTop(boolean reLayoutOnScrollToTop) {
          this.reLayoutOnScrollToTop = reLayoutOnScrollToTop;
      }
  
      private void handleAutoReLayoutWhenScrollToTop() {
          int[] firstVisibleItems = new int[mSpanCount];
          findFirstCompletelyVisibleItemPositions(firstVisibleItems);
          Log.d(TAG, "onScrollStateChanged: firstVisibleItems=" + firstVisibleItems[0] + "-" + firstVisibleItems[1]);
  
          boolean needReLayout = false;
          for (int firstVisibleItem : firstVisibleItems) {
              View child = getChildAt(firstVisibleItem);
              if (child != null) {
                  int top = child.getTop();
                  if (top != 0) {
                      needReLayout = true;
                  }
                  Log.d(TAG, "onScrollStateChanged: top=" + child.getTop());
              }
          }
          if (needReLayout) {
              requestLayout();
          }
      }
  
      @Override
      public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
          super.onDetachedFromWindow(view, recycler);
  
          removeCallbacks(mCheckForGapsRunnable);
          for (int i = 0; i < mSpanCount; i++) {
              mSpans[i].clear();
          }
          // SGLM will require fresh layout call to recover state after detach
          view.requestLayout();
      }
  
      /**
       * Checks for gaps if we've reached to the top of the list.
       * <p>
       * Intermediate gaps created by full span items are tracked via mLaidOutInvalidFullSpan field.
       */
      View hasGapsToFix() {
          int startChildIndex = 0;
          int endChildIndex = getChildCount() - 1;
          BitSet mSpansToCheck = new BitSet(mSpanCount);
          mSpansToCheck.set(0, mSpanCount, true);
  
          final int firstChildIndex, childLimit;
          final int preferredSpanDir = mOrientation == VERTICAL && isLayoutRTL() ? 1 : -1;
  
          if (mShouldReverseLayout) {
              firstChildIndex = endChildIndex;
              childLimit = startChildIndex - 1;
          } else {
              firstChildIndex = startChildIndex;
              childLimit = endChildIndex + 1;
          }
          final int nextChildDiff = firstChildIndex < childLimit ? 1 : -1;
          for (int i = firstChildIndex; i != childLimit; i += nextChildDiff) {
              View child = getChildAt(i);
              FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
              if (mSpansToCheck.get(lp.mSpan.mIndex)) {
                  if (checkSpanForGap(lp.mSpan)) {
                      return child;
                  }
                  mSpansToCheck.clear(lp.mSpan.mIndex);
              }
              if (lp.mFullSpan) {
                  continue; // quick reject
              }
  
              if (i + nextChildDiff != childLimit) {
                  View nextChild = getChildAt(i + nextChildDiff);
                  boolean compareSpans = false;
                  if (mShouldReverseLayout) {
                      // ensure child's end is below nextChild's end
                      int myEnd = mPrimaryOrientation.getDecoratedEnd(child);
                      int nextEnd = mPrimaryOrientation.getDecoratedEnd(nextChild);
                      if (myEnd < nextEnd) {
                          return child; //i should have a better position
                      } else if (myEnd == nextEnd) {
                          compareSpans = true;
                      }
                  } else {
                      int myStart = mPrimaryOrientation.getDecoratedStart(child);
                      int nextStart = mPrimaryOrientation.getDecoratedStart(nextChild);
                      if (myStart > nextStart) {
                          return child; //i should have a better position
                      } else if (myStart == nextStart) {
                          compareSpans = true;
                      }
                  }
                  if (compareSpans) {
                      // equal, check span indices.
                      FlapIndexedStaggeredGridLayoutManager.LayoutParams nextLp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) nextChild.getLayoutParams();
                      if (lp.mSpan.mIndex - nextLp.mSpan.mIndex < 0 != preferredSpanDir < 0) {
                          return child;
                      }
                  }
              }
          }
          // everything looks good
          return null;
      }
  
      private boolean checkSpanForGap(FlapIndexedStaggeredGridLayoutManager.Span span) {
          if (mShouldReverseLayout) {
              if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
                  // if it is full span, it is OK
                  final View endView = span.mViews.get(span.mViews.size() - 1);
                  final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = span.getLayoutParams(endView);
                  return !lp.mFullSpan;
              }
          } else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
              // if it is full span, it is OK
              final View startView = span.mViews.get(0);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = span.getLayoutParams(startView);
              return !lp.mFullSpan;
          }
          return false;
      }
  
      /**
       * Sets the number of spans for the layout. This will invalidate all of the span assignments
       * for Views.
       * <p>
       * Calling this method will automatically result in a new layout request unless the spanCount
       * parameter is equal to current span count.
       *
       * @param spanCount Number of spans to layout
       */
      public void setSpanCount(int spanCount) {
          assertNotInLayoutOrScroll(null);
          if (spanCount != mSpanCount) {
              invalidateSpanAssignments();
              mSpanCount = spanCount;
              mRemainingSpans = new BitSet(mSpanCount);
              mSpans = new FlapIndexedStaggeredGridLayoutManager.Span[mSpanCount];
              for (int i = 0; i < mSpanCount; i++) {
                  mSpans[i] = new FlapIndexedStaggeredGridLayoutManager.Span(i);
              }
              requestLayout();
          }
      }
  
      /**
       * Sets the orientation of the layout. StaggeredGridLayoutManager will do its best to keep
       * scroll position if this method is called after views are laid out.
       *
       * @param orientation {@link #HORIZONTAL} or {@link #VERTICAL}
       */
      public void setOrientation(int orientation) {
          if (orientation != HORIZONTAL && orientation != VERTICAL) {
              throw new IllegalArgumentException("invalid orientation.");
          }
          assertNotInLayoutOrScroll(null);
          if (orientation == mOrientation) {
              return;
          }
          mOrientation = orientation;
          OrientationHelper tmp = mPrimaryOrientation;
          mPrimaryOrientation = mSecondaryOrientation;
          mSecondaryOrientation = tmp;
          requestLayout();
      }
  
      /**
       * Sets whether LayoutManager should start laying out items from the end of the UI. The order
       * items are traversed is not affected by this call.
       * <p>
       * For vertical layout, if it is set to <code>true</code>, first item will be at the bottom of
       * the list.
       * <p>
       * For horizontal layouts, it depends on the layout direction.
       * When set to true, If {@link RecyclerView} is LTR, than it will layout from RTL, if
       * {@link RecyclerView}} is RTL, it will layout from LTR.
       *
       * @param reverseLayout Whether layout should be in reverse or not
       */
      public void setReverseLayout(boolean reverseLayout) {
          assertNotInLayoutOrScroll(null);
          if (mPendingSavedState != null && mPendingSavedState.mReverseLayout != reverseLayout) {
              mPendingSavedState.mReverseLayout = reverseLayout;
          }
          mReverseLayout = reverseLayout;
          requestLayout();
      }
  
      /**
       * Returns the current gap handling strategy for StaggeredGridLayoutManagerT.
       * <p>
       * Staggered grid may have gaps in the layout due to changes in the adapter. To avoid gaps,
       * StaggeredGridLayoutManager provides 2 options. Check {@link #GAP_HANDLING_NONE} and
       * {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} for details.
       * <p>
       * By default, StaggeredGridLayoutManager uses {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}.
       *
       * @return Current gap handling strategy.
       * @see #setGapStrategy(int)
       * @see #GAP_HANDLING_NONE
       * @see #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS
       */
      public int getGapStrategy() {
          return mGapStrategy;
      }
  
      /**
       * Sets the gap handling strategy for StaggeredGridLayoutManagerT. If the gapStrategy parameter
       * is different than the current strategy, calling this method will trigger a layout request.
       *
       * @param gapStrategy The new gap handling strategy. Should be
       *                    {@link #GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS} or {@link
       *                    #GAP_HANDLING_NONE}.
       * @see #getGapStrategy()
       */
      public void setGapStrategy(int gapStrategy) {
          assertNotInLayoutOrScroll(null);
          if (gapStrategy == mGapStrategy) {
              return;
          }
          if (gapStrategy != GAP_HANDLING_NONE
                  && gapStrategy != GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS) {
              throw new IllegalArgumentException("invalid gap strategy. Must be GAP_HANDLING_NONE "
                      + "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
          }
          mGapStrategy = gapStrategy;
          requestLayout();
      }
  
      @Override
      public void assertNotInLayoutOrScroll(String message) {
          if (mPendingSavedState == null) {
              super.assertNotInLayoutOrScroll(message);
          }
      }
  
      /**
       * Returns the number of spans laid out by StaggeredGridLayoutManagerT.
       *
       * @return Number of spans in the layout
       */
      public int getSpanCount() {
          return mSpanCount;
      }
  
      /**
       * For consistency, StaggeredGridLayoutManager keeps a mapping between spans and items.
       * <p>
       * If you need to cancel current assignments, you can call this method which will clear all
       * assignments and request a new layout.
       */
      public void invalidateSpanAssignments() {
          mLazySpanLookup.clear();
          requestLayout();
      }
  
      /**
       * Calculates the views' layout order. (e.g. from end to start or start to end)
       * RTL layout support is applied automatically. So if layout is RTL and
       * {@link #getReverseLayout()} is {@code true}, elements will be laid out starting from left.
       */
      private void resolveShouldLayoutReverse() {
          // A == B is the same result, but we rather keep it readable
          if (mOrientation == VERTICAL || !isLayoutRTL()) {
              mShouldReverseLayout = mReverseLayout;
          } else {
              mShouldReverseLayout = !mReverseLayout;
          }
      }
  
      boolean isLayoutRTL() {
          return getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
      }
  
      /**
       * Returns whether views are laid out in reverse order or not.
       * <p>
       * Not that this value is not affected by RecyclerView's layout direction.
       *
       * @return True if layout is reversed, false otherwise
       * @see #setReverseLayout(boolean)
       */
      public boolean getReverseLayout() {
          return mReverseLayout;
      }
  
      @Override
      public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
          // we don't like it to wrap content in our non-scroll direction.
          final int width, height;
          final int horizontalPadding = getPaddingLeft() + getPaddingRight();
          final int verticalPadding = getPaddingTop() + getPaddingBottom();
          if (mOrientation == VERTICAL) {
              final int usedHeight = childrenBounds.height() + verticalPadding;
              height = chooseSize(hSpec, usedHeight, getMinimumHeight());
              width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding,
                      getMinimumWidth());
          } else {
              final int usedWidth = childrenBounds.width() + horizontalPadding;
              width = chooseSize(wSpec, usedWidth, getMinimumWidth());
              height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding,
                      getMinimumHeight());
          }
          setMeasuredDimension(width, height);
      }
  
      @Override
      public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
          try {
              onLayoutChildren(recycler, state, true);
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  
      @Override
      public void onAdapterChanged(@Nullable RecyclerView.Adapter oldAdapter,
                                   @Nullable RecyclerView.Adapter newAdapter) {
          // RV will remove all views so we should clear all spans and assignments of views into spans
          mLazySpanLookup.clear();
          for (int i = 0; i < mSpanCount; i++) {
              mSpans[i].clear();
          }
      }
  
      private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state,
                                    boolean shouldCheckForGaps) {
          final FlapIndexedStaggeredGridLayoutManager.AnchorInfo anchorInfo = mAnchorInfo;
          if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
              if (state.getItemCount() == 0) {
                  removeAndRecycleAllViews(recycler);
                  anchorInfo.reset();
                  return;
              }
          }
  
          boolean recalculateAnchor = !anchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
                  || mPendingSavedState != null;
          if (recalculateAnchor) {
              anchorInfo.reset();
              if (mPendingSavedState != null) {
                  applyPendingSavedState(anchorInfo);
              } else {
                  resolveShouldLayoutReverse();
                  anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
              }
              updateAnchorInfoForLayout(state, anchorInfo);
              anchorInfo.mValid = true;
          }
          if (mPendingSavedState == null && mPendingScrollPosition == RecyclerView.NO_POSITION) {
              if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd
                      || isLayoutRTL() != mLastLayoutRTL) {
                  mLazySpanLookup.clear();
                  anchorInfo.mInvalidateOffsets = true;
              }
          }
  
          if (getChildCount() > 0 && (mPendingSavedState == null
                  || mPendingSavedState.mSpanOffsetsSize < 1)) {
              if (anchorInfo.mInvalidateOffsets) {
                  for (int i = 0; i < mSpanCount; i++) {
                      // Scroll to position is set, clear.
                      mSpans[i].clear();
                      if (anchorInfo.mOffset != INVALID_OFFSET) {
                          mSpans[i].setLine(anchorInfo.mOffset);
                      }
                  }
              } else {
                  if (recalculateAnchor || mAnchorInfo.mSpanReferenceLines == null) {
                      for (int i = 0; i < mSpanCount; i++) {
                          mSpans[i].cacheReferenceLineAndClear(mShouldReverseLayout,
                                  anchorInfo.mOffset);
                      }
                      mAnchorInfo.saveSpanReferenceLines(mSpans);
                  } else {
                      for (int i = 0; i < mSpanCount; i++) {
                          final FlapIndexedStaggeredGridLayoutManager.Span span = mSpans[i];
                          span.clear();
                          span.setLine(mAnchorInfo.mSpanReferenceLines[i]);
                      }
                  }
              }
          }
          detachAndScrapAttachedViews(recycler);
          mLayoutState.mRecycle = false;
          mLaidOutInvalidFullSpan = false;
          updateMeasureSpecs(mSecondaryOrientation.getTotalSpace());
          updateLayoutState(anchorInfo.mPosition, state);
          if (anchorInfo.mLayoutFromEnd) {
              // Layout start.
              setLayoutStateDirection(IndexedLayoutState.LAYOUT_START);
              fill(recycler, mLayoutState, state);
              // Layout end.
              setLayoutStateDirection(IndexedLayoutState.LAYOUT_END);
              mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
              fill(recycler, mLayoutState, state);
          } else {
              // Layout end.
              setLayoutStateDirection(IndexedLayoutState.LAYOUT_END);
              fill(recycler, mLayoutState, state);
              // Layout start.
              setLayoutStateDirection(IndexedLayoutState.LAYOUT_START);
              mLayoutState.mCurrentPosition = anchorInfo.mPosition + mLayoutState.mItemDirection;
              fill(recycler, mLayoutState, state);
          }
  
          repositionToWrapContentIfNecessary();
  
          if (getChildCount() > 0) {
              if (mShouldReverseLayout) {
                  fixEndGap(recycler, state, true);
                  fixStartGap(recycler, state, false);
              } else {
                  fixStartGap(recycler, state, true);
                  fixEndGap(recycler, state, false);
              }
          }
          boolean hasGaps = false;
          if (shouldCheckForGaps && !state.isPreLayout()) {
              final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
                      && getChildCount() > 0
                      && (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
              if (needToCheckForGaps) {
                  removeCallbacks(mCheckForGapsRunnable);
                  if (checkForGaps()) {
                      hasGaps = true;
                  }
              }
          }
          if (state.isPreLayout()) {
              mAnchorInfo.reset();
          }
          mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
          mLastLayoutRTL = isLayoutRTL();
          if (hasGaps) {
              mAnchorInfo.reset();
              onLayoutChildren(recycler, state, false);
          }
      }
  
      @Override
      public void onLayoutCompleted(RecyclerView.State state) {
          super.onLayoutCompleted(state);
          mPendingScrollPosition = RecyclerView.NO_POSITION;
          mPendingScrollPositionOffset = INVALID_OFFSET;
          mPendingSavedState = null; // we don't need this anymore
          mAnchorInfo.reset();
      }
  
      private void repositionToWrapContentIfNecessary() {
          if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
              return; // nothing to do
          }
          float maxSize = 0;
          final int childCount = getChildCount();
          for (int i = 0; i < childCount; i++) {
              View child = getChildAt(i);
              float size = mSecondaryOrientation.getDecoratedMeasurement(child);
              if (size < maxSize) {
                  continue;
              }
              FlapIndexedStaggeredGridLayoutManager.LayoutParams layoutParams = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
              if (layoutParams.isFullSpan()) {
                  size = 1f * size / mSpanCount;
              }
              maxSize = Math.max(maxSize, size);
          }
          int before = mSizePerSpan;
          int desired = Math.round(maxSize * mSpanCount);
          if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) {
              desired = Math.min(desired, mSecondaryOrientation.getTotalSpace());
          }
          updateMeasureSpecs(desired);
          if (mSizePerSpan == before) {
              return; // nothing has changed
          }
          for (int i = 0; i < childCount; i++) {
              View child = getChildAt(i);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
              if (lp.mFullSpan) {
                  continue;
              }
              if (isLayoutRTL() && mOrientation == VERTICAL) {
                  int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan;
                  int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before;
                  child.offsetLeftAndRight(newOffset - prevOffset);
              } else {
                  int newOffset = lp.mSpan.mIndex * mSizePerSpan;
                  int prevOffset = lp.mSpan.mIndex * before;
                  if (mOrientation == VERTICAL) {
                      child.offsetLeftAndRight(newOffset - prevOffset);
                  } else {
                      child.offsetTopAndBottom(newOffset - prevOffset);
                  }
              }
          }
      }
  
      private void applyPendingSavedState(FlapIndexedStaggeredGridLayoutManager.AnchorInfo anchorInfo) {
          if (DEBUG) {
              Log.d(TAG, "found saved state: " + mPendingSavedState);
          }
          if (mPendingSavedState.mSpanOffsetsSize > 0) {
              if (mPendingSavedState.mSpanOffsetsSize == mSpanCount) {
                  for (int i = 0; i < mSpanCount; i++) {
                      mSpans[i].clear();
                      int line = mPendingSavedState.mSpanOffsets[i];
                      if (line != FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE) {
                          if (mPendingSavedState.mAnchorLayoutFromEnd) {
                              line += mPrimaryOrientation.getEndAfterPadding();
                          } else {
                              line += mPrimaryOrientation.getStartAfterPadding();
                          }
                      }
                      mSpans[i].setLine(line);
                  }
              } else {
                  mPendingSavedState.invalidateSpanInfo();
                  mPendingSavedState.mAnchorPosition = mPendingSavedState.mVisibleAnchorPosition;
              }
          }
          mLastLayoutRTL = mPendingSavedState.mLastLayoutRTL;
          setReverseLayout(mPendingSavedState.mReverseLayout);
          resolveShouldLayoutReverse();
  
          if (mPendingSavedState.mAnchorPosition != RecyclerView.NO_POSITION) {
              mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
              anchorInfo.mLayoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
          } else {
              anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
          }
          if (mPendingSavedState.mSpanLookupSize > 1) {
              mLazySpanLookup.mData = mPendingSavedState.mSpanLookup;
              mLazySpanLookup.mFullSpanItems = mPendingSavedState.mFullSpanItems;
          }
      }
  
      void updateAnchorInfoForLayout(RecyclerView.State state, FlapIndexedStaggeredGridLayoutManager.AnchorInfo anchorInfo) {
          if (updateAnchorFromPendingData(state, anchorInfo)) {
              return;
          }
          if (updateAnchorFromChildren(state, anchorInfo)) {
              return;
          }
          if (DEBUG) {
              Log.d(TAG, "Deciding anchor info from fresh state");
          }
          anchorInfo.assignCoordinateFromPadding();
          anchorInfo.mPosition = 0;
      }
  
      private boolean updateAnchorFromChildren(RecyclerView.State state, FlapIndexedStaggeredGridLayoutManager.AnchorInfo anchorInfo) {
          // We don't recycle views out of adapter order. This way, we can rely on the first or
          // last child as the anchor position.
          // Layout direction may change but we should select the child depending on the latest
          // layout direction. Otherwise, we'll choose the wrong child.
          anchorInfo.mPosition = mLastLayoutFromEnd
                  ? findLastReferenceChildPosition(state.getItemCount())
                  : findFirstReferenceChildPosition(state.getItemCount());
          anchorInfo.mOffset = INVALID_OFFSET;
          return true;
      }
  
      boolean updateAnchorFromPendingData(RecyclerView.State state, FlapIndexedStaggeredGridLayoutManager.AnchorInfo anchorInfo) {
          // Validate scroll position if exists.
          if (state.isPreLayout() || mPendingScrollPosition == RecyclerView.NO_POSITION) {
              return false;
          }
          // Validate it.
          if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
              mPendingScrollPosition = RecyclerView.NO_POSITION;
              mPendingScrollPositionOffset = INVALID_OFFSET;
              return false;
          }
  
          if (mPendingSavedState == null || mPendingSavedState.mAnchorPosition == RecyclerView.NO_POSITION
                  || mPendingSavedState.mSpanOffsetsSize < 1) {
              // If item is visible, make it fully visible.
              final View child = findViewByPosition(mPendingScrollPosition);
              if (child != null) {
                  // Use regular anchor position, offset according to pending offset and target
                  // child
                  anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
                          : getFirstChildPosition();
                  if (mPendingScrollPositionOffset != INVALID_OFFSET) {
                      if (anchorInfo.mLayoutFromEnd) {
                          final int target = mPrimaryOrientation.getEndAfterPadding()
                                  - mPendingScrollPositionOffset;
                          anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedEnd(child);
                      } else {
                          final int target = mPrimaryOrientation.getStartAfterPadding()
                                  + mPendingScrollPositionOffset;
                          anchorInfo.mOffset = target - mPrimaryOrientation.getDecoratedStart(child);
                      }
                      return true;
                  }
  
                  // no offset provided. Decide according to the child location
                  final int childSize = mPrimaryOrientation.getDecoratedMeasurement(child);
                  if (childSize > mPrimaryOrientation.getTotalSpace()) {
                      // Item does not fit. Fix depending on layout direction.
                      anchorInfo.mOffset = anchorInfo.mLayoutFromEnd
                              ? mPrimaryOrientation.getEndAfterPadding()
                              : mPrimaryOrientation.getStartAfterPadding();
                      return true;
                  }
  
                  final int startGap = mPrimaryOrientation.getDecoratedStart(child)
                          - mPrimaryOrientation.getStartAfterPadding();
                  if (startGap < 0) {
                      anchorInfo.mOffset = -startGap;
                      return true;
                  }
                  final int endGap = mPrimaryOrientation.getEndAfterPadding()
                          - mPrimaryOrientation.getDecoratedEnd(child);
                  if (endGap < 0) {
                      anchorInfo.mOffset = endGap;
                      return true;
                  }
                  // child already visible. just layout as usual
                  anchorInfo.mOffset = INVALID_OFFSET;
              } else {
                  // Child is not visible. Set anchor coordinate depending on in which direction
                  // child will be visible.
                  anchorInfo.mPosition = mPendingScrollPosition;
                  if (mPendingScrollPositionOffset == INVALID_OFFSET) {
                      final int position = calculateScrollDirectionForPosition(
                              anchorInfo.mPosition);
                      anchorInfo.mLayoutFromEnd = position == IndexedLayoutState.LAYOUT_END;
                      anchorInfo.assignCoordinateFromPadding();
                  } else {
                      anchorInfo.assignCoordinateFromPadding(mPendingScrollPositionOffset);
                  }
                  anchorInfo.mInvalidateOffsets = true;
              }
          } else {
              anchorInfo.mOffset = INVALID_OFFSET;
              anchorInfo.mPosition = mPendingScrollPosition;
          }
          return true;
      }
  
      void updateMeasureSpecs(int totalSpace) {
          mSizePerSpan = totalSpace / mSpanCount;
          //noinspection ResourceType
          mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
                  totalSpace, mSecondaryOrientation.getMode());
      }
  
      boolean supportsPredictiveItemAnimations = false;
  
      @Override
      public boolean supportsPredictiveItemAnimations() {
  //        return mPendingSavedState == null;
          return supportsPredictiveItemAnimations;
      }
  
      /**
       * Returns the adapter position of the first visible view for each span.
       * <p>
       * Note that, this value is not affected by layout orientation or item order traversal.
       * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
       * not in the layout.
       * <p>
       * If RecyclerView has item decorators, they will be considered in calculations as well.
       * <p>
       * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
       * views are ignored in this method.
       *
       * @param into An array to put the results into. If you don't provide any, LayoutManager will
       *             create a new one.
       * @return The adapter position of the first visible item in each span. If a span does not have
       * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
       * @see #findFirstCompletelyVisibleItemPositions(int[])
       * @see #findLastVisibleItemPositions(int[])
       */
      public int[] findFirstVisibleItemPositions(int[] into) {
          if (into == null) {
              into = new int[mSpanCount];
          } else if (into.length < mSpanCount) {
              throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                      + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
          }
          for (int i = 0; i < mSpanCount; i++) {
              into[i] = mSpans[i].findFirstVisibleItemPosition();
          }
          return into;
      }
  
      /**
       * Returns the adapter position of the first completely visible view for each span.
       * <p>
       * Note that, this value is not affected by layout orientation or item order traversal.
       * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
       * not in the layout.
       * <p>
       * If RecyclerView has item decorators, they will be considered in calculations as well.
       * <p>
       * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
       * views are ignored in this method.
       *
       * @param into An array to put the results into. If you don't provide any, LayoutManager will
       *             create a new one.
       * @return The adapter position of the first fully visible item in each span. If a span does
       * not have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
       * @see #findFirstVisibleItemPositions(int[])
       * @see #findLastCompletelyVisibleItemPositions(int[])
       */
      public int[] findFirstCompletelyVisibleItemPositions(int[] into) {
          if (into == null) {
              into = new int[mSpanCount];
          } else if (into.length < mSpanCount) {
              throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                      + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
          }
          for (int i = 0; i < mSpanCount; i++) {
              into[i] = mSpans[i].findFirstCompletelyVisibleItemPosition();
          }
          return into;
      }
  
      /**
       * Returns the adapter position of the last visible view for each span.
       * <p>
       * Note that, this value is not affected by layout orientation or item order traversal.
       * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
       * not in the layout.
       * <p>
       * If RecyclerView has item decorators, they will be considered in calculations as well.
       * <p>
       * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
       * views are ignored in this method.
       *
       * @param into An array to put the results into. If you don't provide any, LayoutManager will
       *             create a new one.
       * @return The adapter position of the last visible item in each span. If a span does not have
       * any items, {@link RecyclerView#NO_POSITION} is returned for that span.
       * @see #findLastCompletelyVisibleItemPositions(int[])
       * @see #findFirstVisibleItemPositions(int[])
       */
      public int[] findLastVisibleItemPositions(int[] into) {
          if (into == null) {
              into = new int[mSpanCount];
          } else if (into.length < mSpanCount) {
              throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                      + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
          }
          for (int i = 0; i < mSpanCount; i++) {
              into[i] = mSpans[i].findLastVisibleItemPosition();
          }
          return into;
      }
  
      /**
       * Returns the adapter position of the last completely visible view for each span.
       * <p>
       * Note that, this value is not affected by layout orientation or item order traversal.
       * ({@link #setReverseLayout(boolean)}). Views are sorted by their positions in the adapter,
       * not in the layout.
       * <p>
       * If RecyclerView has item decorators, they will be considered in calculations as well.
       * <p>
       * StaggeredGridLayoutManager may pre-cache some views that are not necessarily visible. Those
       * views are ignored in this method.
       *
       * @param into An array to put the results into. If you don't provide any, LayoutManager will
       *             create a new one.
       * @return The adapter position of the last fully visible item in each span. If a span does not
       * have any items, {@link RecyclerView#NO_POSITION} is returned for that span.
       * @see #findFirstCompletelyVisibleItemPositions(int[])
       * @see #findLastVisibleItemPositions(int[])
       */
      public int[] findLastCompletelyVisibleItemPositions(int[] into) {
          if (into == null) {
              into = new int[mSpanCount];
          } else if (into.length < mSpanCount) {
              throw new IllegalArgumentException("Provided int[]'s size must be more than or equal"
                      + " to span count. Expected:" + mSpanCount + ", array size:" + into.length);
          }
          for (int i = 0; i < mSpanCount; i++) {
              into[i] = mSpans[i].findLastCompletelyVisibleItemPosition();
          }
          return into;
      }
  
      @Override
      public int computeHorizontalScrollOffset(RecyclerView.State state) {
          return computeScrollOffset(state);
      }
  
      private int computeScrollOffset(RecyclerView.State state) {
          if (getChildCount() == 0) {
              return 0;
          }
          return IndexedScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
                  findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
                  findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
                  this, mSmoothScrollbarEnabled, mShouldReverseLayout);
      }
  
      @Override
      public int computeVerticalScrollOffset(RecyclerView.State state) {
          return computeScrollOffset(state);
      }
  
      @Override
      public int computeHorizontalScrollExtent(RecyclerView.State state) {
          return computeScrollExtent(state);
      }
  
      private int computeScrollExtent(RecyclerView.State state) {
          if (getChildCount() == 0) {
              return 0;
          }
          return IndexedScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
                  findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
                  findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
                  this, mSmoothScrollbarEnabled);
      }
  
      @Override
      public int computeVerticalScrollExtent(RecyclerView.State state) {
          return computeScrollExtent(state);
      }
  
      @Override
      public int computeHorizontalScrollRange(RecyclerView.State state) {
          return computeScrollRange(state);
      }
  
      private int computeScrollRange(RecyclerView.State state) {
          if (getChildCount() == 0) {
              return 0;
          }
          return IndexedScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
                  findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled),
                  findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled),
                  this, mSmoothScrollbarEnabled);
      }
  
      @Override
      public int computeVerticalScrollRange(RecyclerView.State state) {
          return computeScrollRange(state);
      }
  
      private void measureChildWithDecorationsAndMargin(View child, FlapIndexedStaggeredGridLayoutManager.LayoutParams lp,
                                                        boolean alreadyMeasured) {
          if (lp.mFullSpan) {
              if (mOrientation == VERTICAL) {
                  measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
                          getChildMeasureSpec(
                                  getHeight(),
                                  getHeightMode(),
                                  getPaddingTop() + getPaddingBottom(),
                                  lp.height,
                                  true),
                          alreadyMeasured);
              } else {
                  measureChildWithDecorationsAndMargin(
                          child,
                          getChildMeasureSpec(
                                  getWidth(),
                                  getWidthMode(),
                                  getPaddingLeft() + getPaddingRight(),
                                  lp.width,
                                  true),
                          mFullSizeSpec,
                          alreadyMeasured);
              }
          } else {
              if (mOrientation == VERTICAL) {
                  // Padding for width measure spec is 0 because left and right padding were already
                  // factored into mSizePerSpan.
                  measureChildWithDecorationsAndMargin(
                          child,
                          getChildMeasureSpec(
                                  mSizePerSpan,
                                  getWidthMode(),
                                  0,
                                  lp.width,
                                  false),
                          getChildMeasureSpec(
                                  getHeight(),
                                  getHeightMode(),
                                  getPaddingTop() + getPaddingBottom(),
                                  lp.height,
                                  true),
                          alreadyMeasured);
              } else {
                  // Padding for height measure spec is 0 because top and bottom padding were already
                  // factored into mSizePerSpan.
                  measureChildWithDecorationsAndMargin(
                          child,
                          getChildMeasureSpec(
                                  getWidth(),
                                  getWidthMode(),
                                  getPaddingLeft() + getPaddingRight(),
                                  lp.width,
                                  true),
                          getChildMeasureSpec(
                                  mSizePerSpan,
                                  getHeightMode(),
                                  0,
                                  lp.height,
                                  false),
                          alreadyMeasured);
              }
          }
      }
  
      private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
                                                        int heightSpec, boolean alreadyMeasured) {
          calculateItemDecorationsForChild(child, mTmpRect);
          FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
          widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
                  lp.rightMargin + mTmpRect.right);
          heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
                  lp.bottomMargin + mTmpRect.bottom);
          final boolean measure = alreadyMeasured;
  //                ? shouldReMeasureChild(child, widthSpec, heightSpec, lp)
  //                : shouldMeasureChild(child, widthSpec, heightSpec, lp);
  //        if (measure) {
          child.measure(widthSpec, heightSpec);
  //        }
  
      }
  
      private int updateSpecWithExtra(int spec, int startInset, int endInset) {
          if (startInset == 0 && endInset == 0) {
              return spec;
          }
          final int mode = View.MeasureSpec.getMode(spec);
          if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
              return View.MeasureSpec.makeMeasureSpec(
                      Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode);
          }
          return spec;
      }
  
      @Override
      public void onRestoreInstanceState(Parcelable state) {
          if (state instanceof FlapIndexedStaggeredGridLayoutManager.SavedState) {
              mPendingSavedState = (FlapIndexedStaggeredGridLayoutManager.SavedState) state;
              if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
                  mPendingSavedState.invalidateAnchorPositionInfo();
                  mPendingSavedState.invalidateSpanInfo();
              }
              requestLayout();
          } else if (DEBUG) {
              Log.d(TAG, "invalid saved state class");
          }
      }
  
      @Override
      public Parcelable onSaveInstanceState() {
          if (mPendingSavedState != null) {
              return new FlapIndexedStaggeredGridLayoutManager.SavedState(mPendingSavedState);
          }
          FlapIndexedStaggeredGridLayoutManager.SavedState state = new FlapIndexedStaggeredGridLayoutManager.SavedState();
          state.mReverseLayout = mReverseLayout;
          state.mAnchorLayoutFromEnd = mLastLayoutFromEnd;
          state.mLastLayoutRTL = mLastLayoutRTL;
  
          if (mLazySpanLookup != null && mLazySpanLookup.mData != null) {
              state.mSpanLookup = mLazySpanLookup.mData;
              state.mSpanLookupSize = state.mSpanLookup.length;
              state.mFullSpanItems = mLazySpanLookup.mFullSpanItems;
          } else {
              state.mSpanLookupSize = 0;
          }
  
          if (getChildCount() > 0) {
              state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
                      : getFirstChildPosition();
              state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
              state.mSpanOffsetsSize = mSpanCount;
              state.mSpanOffsets = new int[mSpanCount];
              for (int i = 0; i < mSpanCount; i++) {
                  int line;
                  if (mLastLayoutFromEnd) {
                      line = mSpans[i].getEndLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE);
                      if (line != FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE) {
                          line -= mPrimaryOrientation.getEndAfterPadding();
                      }
                  } else {
                      line = mSpans[i].getStartLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE);
                      if (line != FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE) {
                          line -= mPrimaryOrientation.getStartAfterPadding();
                      }
                  }
                  state.mSpanOffsets[i] = line;
              }
          } else {
              state.mAnchorPosition = RecyclerView.NO_POSITION;
              state.mVisibleAnchorPosition = RecyclerView.NO_POSITION;
              state.mSpanOffsetsSize = 0;
          }
          if (DEBUG) {
              Log.d(TAG, "saved state:\n" + state);
          }
          return state;
      }
  
      @Override
      public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
          super.onInitializeAccessibilityEvent(event);
          if (getChildCount() > 0) {
              final View start = findFirstVisibleItemClosestToStart(false);
              final View end = findFirstVisibleItemClosestToEnd(false);
              if (start == null || end == null) {
                  return;
              }
              final int startPos = getPosition(start);
              final int endPos = getPosition(end);
              if (startPos < endPos) {
                  event.setFromIndex(startPos);
                  event.setToIndex(endPos);
              } else {
                  event.setFromIndex(endPos);
                  event.setToIndex(startPos);
              }
          }
      }
  
      /**
       * Finds the first fully visible child to be used as an anchor child if span count changes when
       * state is restored. If no children is fully visible, returns a partially visible child instead
       * of returning null.
       */
      int findFirstVisibleItemPositionInt() {
          final View first = mShouldReverseLayout ? findFirstVisibleItemClosestToEnd(true) :
                  findFirstVisibleItemClosestToStart(true);
          return first == null ? RecyclerView.NO_POSITION : getPosition(first);
      }
  
      /**
       * This is for internal use. Not necessarily the child closest to start but the first child
       * we find that matches the criteria.
       * This method does not do any sorting based on child's start coordinate, instead, it uses
       * children order.
       */
      View findFirstVisibleItemClosestToStart(boolean fullyVisible) {
          final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
          final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
          final int limit = getChildCount();
          View partiallyVisible = null;
          for (int i = 0; i < limit; i++) {
              final View child = getChildAt(i);
              final int childStart = mPrimaryOrientation.getDecoratedStart(child);
              final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
              if (childEnd <= boundsStart || childStart >= boundsEnd) {
                  continue; // not visible at all
              }
              if (childStart >= boundsStart || !fullyVisible) {
                  // when checking for start, it is enough even if part of the child's top is visible
                  // as long as fully visible is not requested.
                  return child;
              }
              if (partiallyVisible == null) {
                  partiallyVisible = child;
              }
          }
          return partiallyVisible;
      }
  
      /**
       * This is for internal use. Not necessarily the child closest to bottom but the first child
       * we find that matches the criteria.
       * This method does not do any sorting based on child's end coordinate, instead, it uses
       * children order.
       */
      View findFirstVisibleItemClosestToEnd(boolean fullyVisible) {
          final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
          final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
          View partiallyVisible = null;
          for (int i = getChildCount() - 1; i >= 0; i--) {
              final View child = getChildAt(i);
              final int childStart = mPrimaryOrientation.getDecoratedStart(child);
              final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
              if (childEnd <= boundsStart || childStart >= boundsEnd) {
                  continue; // not visible at all
              }
              if (childEnd <= boundsEnd || !fullyVisible) {
                  // when checking for end, it is enough even if part of the child's bottom is visible
                  // as long as fully visible is not requested.
                  return child;
              }
              if (partiallyVisible == null) {
                  partiallyVisible = child;
              }
          }
          return partiallyVisible;
      }
  
      private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
                             boolean canOffsetChildren) {
          final int maxEndLine = getMaxEnd(Integer.MIN_VALUE);
          if (maxEndLine == Integer.MIN_VALUE) {
              return;
          }
          int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
          int fixOffset;
          if (gap > 0) {
              fixOffset = -scrollBy(-gap, recycler, state);
          } else {
              return; // nothing to fix
          }
          gap -= fixOffset;
          if (canOffsetChildren && gap > 0) {
              mPrimaryOrientation.offsetChildren(gap);
          }
      }
  
      private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
                               boolean canOffsetChildren) {
          final int minStartLine = getMinStart(Integer.MAX_VALUE);
          if (minStartLine == Integer.MAX_VALUE) {
              return;
          }
          int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
          int fixOffset;
          if (gap > 0) {
              fixOffset = scrollBy(gap, recycler, state);
          } else {
              return; // nothing to fix
          }
          gap -= fixOffset;
          if (canOffsetChildren && gap > 0) {
              mPrimaryOrientation.offsetChildren(-gap);
          }
      }
  
      private void updateLayoutState(int anchorPosition, RecyclerView.State state) {
          mLayoutState.mAvailable = 0;
          mLayoutState.mCurrentPosition = anchorPosition;
          int startExtra = 0;
          int endExtra = 0;
          if (isSmoothScrolling()) {
              final int targetPos = state.getTargetScrollPosition();
              if (targetPos != RecyclerView.NO_POSITION) {
                  if (mShouldReverseLayout == targetPos < anchorPosition) {
                      endExtra = mPrimaryOrientation.getTotalSpace();
                  } else {
                      startExtra = mPrimaryOrientation.getTotalSpace();
                  }
              }
          }
  
          // Line of the furthest row.
          final boolean clipToPadding = getClipToPadding();
          if (clipToPadding) {
              mLayoutState.mStartLine = mPrimaryOrientation.getStartAfterPadding() - startExtra;
              mLayoutState.mEndLine = mPrimaryOrientation.getEndAfterPadding() + endExtra;
          } else {
              mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
              mLayoutState.mStartLine = -startExtra;
          }
          mLayoutState.mStopInFocusable = false;
          mLayoutState.mRecycle = true;
          mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED
                  && mPrimaryOrientation.getEnd() == 0;
      }
  
      private void setLayoutStateDirection(int direction) {
          mLayoutState.mLayoutDirection = direction;
          mLayoutState.mItemDirection = (mShouldReverseLayout == (direction == IndexedLayoutState.LAYOUT_START))
                  ? IndexedLayoutState.ITEM_DIRECTION_TAIL : IndexedLayoutState.ITEM_DIRECTION_HEAD;
      }
  
      @Override
      public void offsetChildrenHorizontal(int dx) {
          super.offsetChildrenHorizontal(dx);
          for (int i = 0; i < mSpanCount; i++) {
              mSpans[i].onOffset(dx);
          }
      }
  
      @Override
      public void offsetChildrenVertical(int dy) {
          super.offsetChildrenVertical(dy);
          for (int i = 0; i < mSpanCount; i++) {
              mSpans[i].onOffset(dy);
          }
      }
  
      @Override
      public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
          handleUpdate(positionStart, itemCount, IndexedAdapterHelper.UpdateOp.REMOVE);
      }
  
      @Override
      public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
          handleUpdate(positionStart, itemCount, IndexedAdapterHelper.UpdateOp.ADD);
      }
  
      @Override
      public void onItemsChanged(RecyclerView recyclerView) {
          mLazySpanLookup.clear();
          requestLayout();
      }
  
      @Override
      public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
          handleUpdate(from, to, IndexedAdapterHelper.UpdateOp.MOVE);
      }
  
      @Override
      public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount,
                                 Object payload) {
          handleUpdate(positionStart, itemCount, IndexedAdapterHelper.UpdateOp.UPDATE);
      }
  
      /**
       * Checks whether it should invalidate span assignments in response to an adapter change.
       */
      private void handleUpdate(int positionStart, int itemCountOrToPosition, int cmd) {
          int minPosition = mShouldReverseLayout ? getLastChildPosition() : getFirstChildPosition();
          final int affectedRangeEnd; // exclusive
          final int affectedRangeStart; // inclusive
  
          if (cmd == IndexedAdapterHelper.UpdateOp.MOVE) {
              if (positionStart < itemCountOrToPosition) {
                  affectedRangeEnd = itemCountOrToPosition + 1;
                  affectedRangeStart = positionStart;
              } else {
                  affectedRangeEnd = positionStart + 1;
                  affectedRangeStart = itemCountOrToPosition;
              }
          } else {
              affectedRangeStart = positionStart;
              affectedRangeEnd = positionStart + itemCountOrToPosition;
          }
  
          mLazySpanLookup.invalidateAfter(affectedRangeStart);
          switch (cmd) {
              case IndexedAdapterHelper.UpdateOp.ADD:
                  mLazySpanLookup.offsetForAddition(positionStart, itemCountOrToPosition);
                  break;
              case IndexedAdapterHelper.UpdateOp.REMOVE:
                  mLazySpanLookup.offsetForRemoval(positionStart, itemCountOrToPosition);
                  break;
              case IndexedAdapterHelper.UpdateOp.MOVE:
                  // TODO optimize
                  mLazySpanLookup.offsetForRemoval(positionStart, 1);
                  mLazySpanLookup.offsetForAddition(itemCountOrToPosition, 1);
                  break;
          }
  
          if (affectedRangeEnd <= minPosition) {
              return;
          }
  
          int maxPosition = mShouldReverseLayout ? getFirstChildPosition() : getLastChildPosition();
          if (affectedRangeStart <= maxPosition) {
              requestLayout();
          }
      }
  
      private int fill(RecyclerView.Recycler recycler, IndexedLayoutState layoutState,
                       RecyclerView.State state) {
          mRemainingSpans.set(0, mSpanCount, true);
          // The target position we are trying to reach.
          final int targetLine;
  
          // Line of the furthest row.
          if (mLayoutState.mInfinite) {
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
                  targetLine = Integer.MAX_VALUE;
              } else { // LAYOUT_START
                  targetLine = Integer.MIN_VALUE;
              }
          } else {
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
                  targetLine = layoutState.mEndLine + layoutState.mAvailable;
              } else { // LAYOUT_START
                  targetLine = layoutState.mStartLine - layoutState.mAvailable;
              }
          }
  
          updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
          if (DEBUG) {
              Log.d(TAG, "FILLING targetLine: " + targetLine + ","
                      + "remaining spans:" + mRemainingSpans + ", state: " + layoutState);
          }
  
          // the default coordinate to add new view.
          final int defaultNewViewLine = mShouldReverseLayout
                  ? mPrimaryOrientation.getEndAfterPadding()
                  : mPrimaryOrientation.getStartAfterPadding();
          boolean added = false;
          while (layoutState.hasMore(state)
                  && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) {
              View view = layoutState.next(recycler);
              FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = ((FlapIndexedStaggeredGridLayoutManager.LayoutParams) view.getLayoutParams());
              final int position = lp.getViewLayoutPosition();
              final int spanIndex = mLazySpanLookup.getSpan(position);
              FlapIndexedStaggeredGridLayoutManager.Span currentSpan;
              final boolean assignSpan = spanIndex == FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID;
              if (assignSpan) {
                  currentSpan = lp.mFullSpan ? mSpans[0] : getNextSpan(layoutState, lp.getViewAdapterPosition());
                  mLazySpanLookup.setSpan(position, currentSpan);
                  if (DEBUG) {
                      Log.d(TAG, "assigned " + currentSpan.mIndex + " for " + position);
                  }
              } else {
                  if (DEBUG) {
                      Log.d(TAG, "using " + spanIndex + " for pos " + position);
                  }
                  currentSpan = mSpans[spanIndex];
              }
              // assign span before measuring so that item decorators can get updated span index
              lp.mSpan = currentSpan;
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
                  addView(view);
              } else {
                  addView(view, 0);
              }
              measureChildWithDecorationsAndMargin(view, lp, false);
  
              final int start;
              final int end;
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
                  start = lp.mFullSpan ? getMaxEnd(defaultNewViewLine)
                          : currentSpan.getEndLine(defaultNewViewLine);
                  end = start + mPrimaryOrientation.getDecoratedMeasurement(view);
                  if (assignSpan && lp.mFullSpan) {
                      FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fullSpanItem;
                      fullSpanItem = createFullSpanItemFromEnd(start);
                      fullSpanItem.mGapDir = IndexedLayoutState.LAYOUT_START;
                      fullSpanItem.mPosition = position;
                      mLazySpanLookup.addFullSpanItem(fullSpanItem);
                  }
              } else {
                  end = lp.mFullSpan ? getMinStart(defaultNewViewLine)
                          : currentSpan.getStartLine(defaultNewViewLine);
                  start = end - mPrimaryOrientation.getDecoratedMeasurement(view);
                  if (assignSpan && lp.mFullSpan) {
                      FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fullSpanItem;
                      fullSpanItem = createFullSpanItemFromStart(end);
                      fullSpanItem.mGapDir = IndexedLayoutState.LAYOUT_END;
                      fullSpanItem.mPosition = position;
                      mLazySpanLookup.addFullSpanItem(fullSpanItem);
                  }
              }
  
              // check if this item may create gaps in the future
              if (lp.mFullSpan && layoutState.mItemDirection == IndexedLayoutState.ITEM_DIRECTION_HEAD) {
                  if (assignSpan) {
                      mLaidOutInvalidFullSpan = true;
                  } else {
                      final boolean hasInvalidGap;
                      if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
                          hasInvalidGap = !areAllEndsEqual();
                      } else { // layoutState.mLayoutDirection == LAYOUT_START
                          hasInvalidGap = !areAllStartsEqual();
                      }
                      if (hasInvalidGap) {
                          final FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fullSpanItem = mLazySpanLookup
                                  .getFullSpanItem(position);
                          if (fullSpanItem != null) {
                              fullSpanItem.mHasUnwantedGapAfter = true;
                          }
                          mLaidOutInvalidFullSpan = true;
                      }
                  }
              }
              attachViewToSpans(view, lp, layoutState);
              final int otherStart;
              final int otherEnd;
              if (isLayoutRTL() && mOrientation == VERTICAL) {
                  otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() :
                          mSecondaryOrientation.getEndAfterPadding()
                                  - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan;
                  otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view);
              } else {
                  otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
                          : currentSpan.mIndex * mSizePerSpan
                          + mSecondaryOrientation.getStartAfterPadding();
                  otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
              }
  
              if (mOrientation == VERTICAL) {
                  layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
              } else {
                  layoutDecoratedWithMargins(view, start, otherStart, end, otherEnd);
              }
  
              if (lp.mFullSpan) {
                  updateAllRemainingSpans(mLayoutState.mLayoutDirection, targetLine);
              } else {
                  updateRemainingSpans(currentSpan, mLayoutState.mLayoutDirection, targetLine);
              }
              recycle(recycler, mLayoutState);
              if (mLayoutState.mStopInFocusable && view.hasFocusable()) {
                  if (lp.mFullSpan) {
                      mRemainingSpans.clear();
                  } else {
                      mRemainingSpans.set(currentSpan.mIndex, false);
                  }
              }
              added = true;
          }
          if (!added) {
              recycle(recycler, mLayoutState);
          }
          final int diff;
          if (mLayoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_START) {
              final int minStart = getMinStart(mPrimaryOrientation.getStartAfterPadding());
              diff = mPrimaryOrientation.getStartAfterPadding() - minStart;
          } else {
              final int maxEnd = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
              diff = maxEnd - mPrimaryOrientation.getEndAfterPadding();
          }
          return diff > 0 ? Math.min(layoutState.mAvailable, diff) : 0;
      }
  
      private FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem createFullSpanItemFromEnd(int newItemTop) {
          FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = new FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem();
          fsi.mGapPerSpan = new int[mSpanCount];
          for (int i = 0; i < mSpanCount; i++) {
              fsi.mGapPerSpan[i] = newItemTop - mSpans[i].getEndLine(newItemTop);
          }
          return fsi;
      }
  
      private FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem createFullSpanItemFromStart(int newItemBottom) {
          FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = new FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem();
          fsi.mGapPerSpan = new int[mSpanCount];
          for (int i = 0; i < mSpanCount; i++) {
              fsi.mGapPerSpan[i] = mSpans[i].getStartLine(newItemBottom) - newItemBottom;
          }
          return fsi;
      }
  
      private void attachViewToSpans(View view, FlapIndexedStaggeredGridLayoutManager.LayoutParams lp, IndexedLayoutState layoutState) {
          if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
              if (lp.mFullSpan) {
                  appendViewToAllSpans(view);
              } else {
                  lp.mSpan.appendToSpan(view);
              }
          } else {
              if (lp.mFullSpan) {
                  prependViewToAllSpans(view);
              } else {
                  lp.mSpan.prependToSpan(view);
              }
          }
      }
  
      private void recycle(RecyclerView.Recycler recycler, IndexedLayoutState layoutState) {
          if (!layoutState.mRecycle || layoutState.mInfinite) {
              return;
          }
          if (layoutState.mAvailable == 0) {
              // easy, recycle line is still valid
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_START) {
                  recycleFromEnd(recycler, layoutState.mEndLine);
              } else {
                  recycleFromStart(recycler, layoutState.mStartLine);
              }
          } else {
              // scrolling case, recycle line can be shifted by how much space we could cover
              // by adding new views
              if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_START) {
                  // calculate recycle line
                  int scrolled = layoutState.mStartLine - getMaxStart(layoutState.mStartLine);
                  final int line;
                  if (scrolled < 0) {
                      line = layoutState.mEndLine;
                  } else {
                      line = layoutState.mEndLine - Math.min(scrolled, layoutState.mAvailable);
                  }
                  recycleFromEnd(recycler, line);
              } else {
                  // calculate recycle line
                  int scrolled = getMinEnd(layoutState.mEndLine) - layoutState.mEndLine;
                  final int line;
                  if (scrolled < 0) {
                      line = layoutState.mStartLine;
                  } else {
                      line = layoutState.mStartLine + Math.min(scrolled, layoutState.mAvailable);
                  }
                  recycleFromStart(recycler, line);
              }
          }
  
      }
  
      private void appendViewToAllSpans(View view) {
          // traverse in reverse so that we end up assigning full span items to 0
          for (int i = mSpanCount - 1; i >= 0; i--) {
              mSpans[i].appendToSpan(view);
          }
      }
  
      private void prependViewToAllSpans(View view) {
          // traverse in reverse so that we end up assigning full span items to 0
          for (int i = mSpanCount - 1; i >= 0; i--) {
              mSpans[i].prependToSpan(view);
          }
      }
  
      private void updateAllRemainingSpans(int layoutDir, int targetLine) {
          for (int i = 0; i < mSpanCount; i++) {
              if (mSpans[i].mViews.isEmpty()) {
                  continue;
              }
              updateRemainingSpans(mSpans[i], layoutDir, targetLine);
          }
      }
  
      private void updateRemainingSpans(FlapIndexedStaggeredGridLayoutManager.Span span, int layoutDir, int targetLine) {
          final int deletedSize = span.getDeletedSize();
          if (layoutDir == IndexedLayoutState.LAYOUT_START) {
              final int line = span.getStartLine();
              if (line + deletedSize <= targetLine) {
                  mRemainingSpans.set(span.mIndex, false);
              }
          } else {
              final int line = span.getEndLine();
              if (line - deletedSize >= targetLine) {
                  mRemainingSpans.set(span.mIndex, false);
              }
          }
      }
  
      private int getMaxStart(int def) {
          int maxStart = mSpans[0].getStartLine(def);
          for (int i = 1; i < mSpanCount; i++) {
              final int spanStart = mSpans[i].getStartLine(def);
              if (spanStart > maxStart) {
                  maxStart = spanStart;
              }
          }
          return maxStart;
      }
  
      private int getMinStart(int def) {
          int minStart = mSpans[0].getStartLine(def);
          for (int i = 1; i < mSpanCount; i++) {
              final int spanStart = mSpans[i].getStartLine(def);
              if (spanStart < minStart) {
                  minStart = spanStart;
              }
          }
          return minStart;
      }
  
      boolean areAllEndsEqual() {
          int end = mSpans[0].getEndLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE);
          for (int i = 1; i < mSpanCount; i++) {
              if (mSpans[i].getEndLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE) != end) {
                  return false;
              }
          }
          return true;
      }
  
      boolean areAllStartsEqual() {
          int start = mSpans[0].getStartLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE);
          for (int i = 1; i < mSpanCount; i++) {
              if (mSpans[i].getStartLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE) != start) {
                  return false;
              }
          }
          return true;
      }
  
      private int getMaxEnd(int def) {
          int maxEnd = mSpans[0].getEndLine(def);
          for (int i = 1; i < mSpanCount; i++) {
              final int spanEnd = mSpans[i].getEndLine(def);
              if (spanEnd > maxEnd) {
                  maxEnd = spanEnd;
              }
          }
          return maxEnd;
      }
  
      private int getMinEnd(int def) {
          int minEnd = mSpans[0].getEndLine(def);
          for (int i = 1; i < mSpanCount; i++) {
              final int spanEnd = mSpans[i].getEndLine(def);
              if (spanEnd < minEnd) {
                  minEnd = spanEnd;
              }
          }
          return minEnd;
      }
  
      private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
          while (getChildCount() > 0) {
              View child = getChildAt(0);
              if (mPrimaryOrientation.getDecoratedEnd(child) <= line
                      && mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) {
                  FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
                  // Don't recycle the last View in a span not to lose span's start/end lines
                  if (lp.mFullSpan) {
                      for (int j = 0; j < mSpanCount; j++) {
                          if (mSpans[j].mViews.size() == 1) {
                              return;
                          }
                      }
                      for (int j = 0; j < mSpanCount; j++) {
                          mSpans[j].popStart();
                      }
                  } else {
                      if (lp.mSpan.mViews.size() == 1) {
                          return;
                      }
                      lp.mSpan.popStart();
                  }
                  removeAndRecycleView(child, recycler);
              } else {
                  return; // done
              }
          }
      }
  
      private void recycleFromEnd(RecyclerView.Recycler recycler, int line) {
          final int childCount = getChildCount();
          int i;
          for (i = childCount - 1; i >= 0; i--) {
              View child = getChildAt(i);
              if (mPrimaryOrientation.getDecoratedStart(child) >= line
                      && mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) {
                  FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) child.getLayoutParams();
                  // Don't recycle the last View in a span not to lose span's start/end lines
                  if (lp.mFullSpan) {
                      for (int j = 0; j < mSpanCount; j++) {
                          if (mSpans[j].mViews.size() == 1) {
                              return;
                          }
                      }
                      for (int j = 0; j < mSpanCount; j++) {
                          mSpans[j].popEnd();
                      }
                  } else {
                      if (lp.mSpan.mViews.size() == 1) {
                          return;
                      }
                      lp.mSpan.popEnd();
                  }
                  removeAndRecycleView(child, recycler);
              } else {
                  return; // done
              }
          }
      }
  
      /**
       * @return True if last span is the first one we want to fill
       */
      private boolean preferLastSpan(int layoutDir) {
          if (mOrientation == HORIZONTAL) {
              return (layoutDir == IndexedLayoutState.LAYOUT_START) != mShouldReverseLayout;
          }
          return ((layoutDir == IndexedLayoutState.LAYOUT_START) == mShouldReverseLayout) == isLayoutRTL();
      }
  
      /**
       * Finds the span for the next view.
       */
      private FlapIndexedStaggeredGridLayoutManager.Span getNextSpan(IndexedLayoutState layoutState, int viewAdapterPosition) {
          final boolean preferLastSpan = preferLastSpan(layoutState.mLayoutDirection);
          final int startIndex, endIndex, diff;
          if (preferLastSpan) {
              startIndex = mSpanCount - 1;
              endIndex = -1;
              diff = -1;
          } else {
              startIndex = 0;
              endIndex = mSpanCount;
              diff = 1;
          }
          if (layoutState.mLayoutDirection == IndexedLayoutState.LAYOUT_END) {
              FlapIndexedStaggeredGridLayoutManager.Span min = null;
              int minLine = Integer.MAX_VALUE;
              final int defaultLine = mPrimaryOrientation.getStartAfterPadding();
              for (int i = startIndex; i != endIndex; i += diff) {
                  final FlapIndexedStaggeredGridLayoutManager.Span other = mSpans[i];
                  int otherLine = other.getEndLine(defaultLine);
                  if (otherLine < minLine) {
                      min = other;
                      minLine = otherLine;
                  }
                  min = mSpans[viewAdapterPosition % getSpanCount()];
              }
              return min;
          } else {
              FlapIndexedStaggeredGridLayoutManager.Span max = null;
              int maxLine = Integer.MIN_VALUE;
              final int defaultLine = mPrimaryOrientation.getEndAfterPadding();
              for (int i = startIndex; i != endIndex; i += diff) {
                  final FlapIndexedStaggeredGridLayoutManager.Span other = mSpans[i];
                  int otherLine = other.getStartLine(defaultLine);
                  if (otherLine > maxLine) {
                      max = other;
                      maxLine = otherLine;
                  }
              }
              return max;
          }
      }
  
      @Override
      public boolean canScrollVertically() {
          return mOrientation == VERTICAL;
      }
  
      @Override
      public boolean canScrollHorizontally() {
          return mOrientation == HORIZONTAL;
      }
  
      @Override
      public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
                                      RecyclerView.State state) {
          return scrollBy(dx, recycler, state);
      }
  
      @Override
      public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
                                    RecyclerView.State state) {
          return scrollBy(dy, recycler, state);
      }
  
      private int calculateScrollDirectionForPosition(int position) {
          if (getChildCount() == 0) {
              return mShouldReverseLayout ? IndexedLayoutState.LAYOUT_END : IndexedLayoutState.LAYOUT_START;
          }
          final int firstChildPos = getFirstChildPosition();
          return position < firstChildPos != mShouldReverseLayout ? IndexedLayoutState.LAYOUT_START : IndexedLayoutState.LAYOUT_END;
      }
  
      @Override
      public PointF computeScrollVectorForPosition(int targetPosition) {
          final int direction = calculateScrollDirectionForPosition(targetPosition);
          PointF outVector = new PointF();
          if (direction == 0) {
              return null;
          }
          if (mOrientation == HORIZONTAL) {
              outVector.x = direction;
              outVector.y = 0;
          } else {
              outVector.x = 0;
              outVector.y = direction;
          }
          return outVector;
      }
  
      @Override
      public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
                                         int position) {
          LinearSmoothScroller scroller = new LinearSmoothScroller(recyclerView.getContext());
          scroller.setTargetPosition(position);
          startSmoothScroll(scroller);
      }
  
      @Override
      public void scrollToPosition(int position) {
          if (mPendingSavedState != null && mPendingSavedState.mAnchorPosition != position) {
              mPendingSavedState.invalidateAnchorPositionInfo();
          }
          mPendingScrollPosition = position;
          mPendingScrollPositionOffset = INVALID_OFFSET;
          requestLayout();
      }
  
      /**
       * Scroll to the specified adapter position with the given offset from layout start.
       * <p>
       * Note that scroll position change will not be reflected until the next layout call.
       * <p>
       * If you are just trying to make a position visible, use {@link #scrollToPosition(int)}.
       *
       * @param position Index (starting at 0) of the reference item.
       * @param offset   The distance (in pixels) between the start edge of the item view and
       *                 start edge of the RecyclerView.
       * @see #setReverseLayout(boolean)
       * @see #scrollToPosition(int)
       */
      public void scrollToPositionWithOffset(int position, int offset) {
          if (mPendingSavedState != null) {
              mPendingSavedState.invalidateAnchorPositionInfo();
          }
          mPendingScrollPosition = position;
          mPendingScrollPositionOffset = offset;
          requestLayout();
      }
  
      /**
       * @hide
       */
      @Override
      @RestrictTo(LIBRARY)
      public void collectAdjacentPrefetchPositions(int dx, int dy, RecyclerView.State state,
                                                   LayoutPrefetchRegistry layoutPrefetchRegistry) {
          /* This method uses the simplifying assumption that the next N items (where N = span count)
           * will be assigned, one-to-one, to spans, where ordering is based on which span  extends
           * least beyond the viewport.
           *
           * While this simplified model will be incorrect in some cases, it's difficult to know
           * item heights, or whether individual items will be full span prior to construction.
           *
           * While this greedy estimation approach may underestimate the distance to prefetch items,
           * it's very unlikely to overestimate them, so distances can be conservatively used to know
           * the soonest (in terms of scroll distance) a prefetched view may come on screen.
           */
          int delta = (mOrientation == HORIZONTAL) ? dx : dy;
          if (getChildCount() == 0 || delta == 0) {
              // can't support this scroll, so don't bother prefetching
              return;
          }
          prepareLayoutStateForDelta(delta, state);
  
          // build sorted list of distances to end of each span (though we don't care which is which)
          if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
              mPrefetchDistances = new int[mSpanCount];
          }
  
          int itemPrefetchCount = 0;
          for (int i = 0; i < mSpanCount; i++) {
              // compute number of pixels past the edge of the viewport that the current span extends
              int distance = mLayoutState.mItemDirection == IndexedLayoutState.LAYOUT_START
                      ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
                      : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
              if (distance >= 0) {
                  // span extends to the edge, so prefetch next item
                  mPrefetchDistances[itemPrefetchCount] = distance;
                  itemPrefetchCount++;
              }
          }
          Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
  
          // then assign them in order to the next N views (where N = span count)
          for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
              layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition,
                      mPrefetchDistances[i]);
              mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
          }
      }
  
      void prepareLayoutStateForDelta(int delta, RecyclerView.State state) {
          final int referenceChildPosition;
          final int layoutDir;
          if (delta > 0) { // layout towards end
              layoutDir = IndexedLayoutState.LAYOUT_END;
              referenceChildPosition = getLastChildPosition();
          } else {
              layoutDir = IndexedLayoutState.LAYOUT_START;
              referenceChildPosition = getFirstChildPosition();
          }
          mLayoutState.mRecycle = true;
          updateLayoutState(referenceChildPosition, state);
          setLayoutStateDirection(layoutDir);
          mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
          mLayoutState.mAvailable = Math.abs(delta);
      }
  
      int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
          if (getChildCount() == 0 || dt == 0) {
              return 0;
          }
  
          prepareLayoutStateForDelta(dt, state);
          int consumed = fill(recycler, mLayoutState, state);
          final int available = mLayoutState.mAvailable;
          final int totalScroll;
          if (available < consumed) {
              totalScroll = dt;
          } else if (dt < 0) {
              totalScroll = -consumed;
          } else { // dt > 0
              totalScroll = consumed;
          }
          if (DEBUG) {
              Log.d(TAG, "asked " + dt + " scrolled" + totalScroll);
          }
  
          mPrimaryOrientation.offsetChildren(-totalScroll);
          // always reset this if we scroll for a proper save instance state
          mLastLayoutFromEnd = mShouldReverseLayout;
          mLayoutState.mAvailable = 0;
          recycle(recycler, mLayoutState);
          return totalScroll;
      }
  
      int getLastChildPosition() {
          final int childCount = getChildCount();
          return childCount == 0 ? 0 : getPosition(getChildAt(childCount - 1));
      }
  
      int getFirstChildPosition() {
          final int childCount = getChildCount();
          return childCount == 0 ? 0 : getPosition(getChildAt(0));
      }
  
      /**
       * Finds the first View that can be used as an anchor View.
       *
       * @return Position of the View or 0 if it cannot find any such View.
       */
      private int findFirstReferenceChildPosition(int itemCount) {
          final int limit = getChildCount();
          for (int i = 0; i < limit; i++) {
              final View view = getChildAt(i);
              final int position = getPosition(view);
              if (position >= 0 && position < itemCount) {
                  return position;
              }
          }
          return 0;
      }
  
      /**
       * Finds the last View that can be used as an anchor View.
       *
       * @return Position of the View or 0 if it cannot find any such View.
       */
      private int findLastReferenceChildPosition(int itemCount) {
          for (int i = getChildCount() - 1; i >= 0; i--) {
              final View view = getChildAt(i);
              final int position = getPosition(view);
              if (position >= 0 && position < itemCount) {
                  return position;
              }
          }
          return 0;
      }
  
      @SuppressWarnings("deprecation")
      @Override
      public RecyclerView.LayoutParams generateDefaultLayoutParams() {
          if (mOrientation == HORIZONTAL) {
              return new FlapIndexedStaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                      ViewGroup.LayoutParams.MATCH_PARENT);
          } else {
              return new FlapIndexedStaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                      ViewGroup.LayoutParams.WRAP_CONTENT);
          }
      }
  
      @Override
      public RecyclerView.LayoutParams generateLayoutParams(Context c, AttributeSet attrs) {
          return new FlapIndexedStaggeredGridLayoutManager.LayoutParams(c, attrs);
      }
  
      @Override
      public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
          if (lp instanceof ViewGroup.MarginLayoutParams) {
              return new FlapIndexedStaggeredGridLayoutManager.LayoutParams((ViewGroup.MarginLayoutParams) lp);
          } else {
              return new FlapIndexedStaggeredGridLayoutManager.LayoutParams(lp);
          }
      }
  
      @Override
      public boolean checkLayoutParams(RecyclerView.LayoutParams lp) {
          return lp instanceof FlapIndexedStaggeredGridLayoutManager.LayoutParams;
      }
  
      public int getOrientation() {
          return mOrientation;
      }
  
      @Nullable
      @Override
      public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
                                      RecyclerView.State state) {
          if (getChildCount() == 0) {
              return null;
          }
  
          final View directChild = findContainingItemView(focused);
          if (directChild == null) {
              return null;
          }
  
          resolveShouldLayoutReverse();
          final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
          if (layoutDir == IndexedLayoutState.INVALID_LAYOUT) {
              return null;
          }
          FlapIndexedStaggeredGridLayoutManager.LayoutParams prevFocusLayoutParams = (FlapIndexedStaggeredGridLayoutManager.LayoutParams) directChild.getLayoutParams();
          boolean prevFocusFullSpan = prevFocusLayoutParams.mFullSpan;
          final FlapIndexedStaggeredGridLayoutManager.Span prevFocusSpan = prevFocusLayoutParams.mSpan;
          final int referenceChildPosition;
          if (layoutDir == IndexedLayoutState.LAYOUT_END) { // layout towards end
              referenceChildPosition = getLastChildPosition();
          } else {
              referenceChildPosition = getFirstChildPosition();
          }
          updateLayoutState(referenceChildPosition, state);
          setLayoutStateDirection(layoutDir);
  
          mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
          mLayoutState.mAvailable = (int) (MAX_SCROLL_FACTOR * mPrimaryOrientation.getTotalSpace());
          mLayoutState.mStopInFocusable = true;
          mLayoutState.mRecycle = false;
          fill(recycler, mLayoutState, state);
          mLastLayoutFromEnd = mShouldReverseLayout;
          if (!prevFocusFullSpan) {
              View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir);
              if (view != null && view != directChild) {
                  return view;
              }
          }
  
          // either could not find from the desired span or prev view is full span.
          // traverse all spans
          if (preferLastSpan(layoutDir)) {
              for (int i = mSpanCount - 1; i >= 0; i--) {
                  View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
                  if (view != null && view != directChild) {
                      return view;
                  }
              }
          } else {
              for (int i = 0; i < mSpanCount; i++) {
                  View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
                  if (view != null && view != directChild) {
                      return view;
                  }
              }
          }
  
          // Could not find any focusable views from any of the existing spans. Now start the search
          // to find the best unfocusable candidate to become visible on the screen next. The search
          // is done in the same fashion: first, check the views in the desired span and if no
          // candidate is found, traverse the views in all the remaining spans.
          boolean shouldSearchFromStart = !mReverseLayout == (layoutDir == IndexedLayoutState.LAYOUT_START);
          View unfocusableCandidate = null;
          if (!prevFocusFullSpan) {
              unfocusableCandidate = findViewByPosition(shouldSearchFromStart
                      ? prevFocusSpan.findFirstPartiallyVisibleItemPosition() :
                      prevFocusSpan.findLastPartiallyVisibleItemPosition());
              if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
                  return unfocusableCandidate;
              }
          }
  
          if (preferLastSpan(layoutDir)) {
              for (int i = mSpanCount - 1; i >= 0; i--) {
                  if (i == prevFocusSpan.mIndex) {
                      continue;
                  }
                  unfocusableCandidate = findViewByPosition(shouldSearchFromStart
                          ? mSpans[i].findFirstPartiallyVisibleItemPosition() :
                          mSpans[i].findLastPartiallyVisibleItemPosition());
                  if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
                      return unfocusableCandidate;
                  }
              }
          } else {
              for (int i = 0; i < mSpanCount; i++) {
                  unfocusableCandidate = findViewByPosition(shouldSearchFromStart
                          ? mSpans[i].findFirstPartiallyVisibleItemPosition() :
                          mSpans[i].findLastPartiallyVisibleItemPosition());
                  if (unfocusableCandidate != null && unfocusableCandidate != directChild) {
                      return unfocusableCandidate;
                  }
              }
          }
          return null;
      }
  
      /**
       * Converts a focusDirection to orientation.
       *
       * @param focusDirection One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
       *                       {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
       *                       {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
       *                       or 0 for not applicable
       */
      private int convertFocusDirectionToLayoutDirection(int focusDirection) {
          switch (focusDirection) {
              case View.FOCUS_BACKWARD:
                  if (mOrientation == VERTICAL) {
                      return IndexedLayoutState.LAYOUT_START;
                  } else if (isLayoutRTL()) {
                      return IndexedLayoutState.LAYOUT_END;
                  } else {
                      return IndexedLayoutState.LAYOUT_START;
                  }
              case View.FOCUS_FORWARD:
                  if (mOrientation == VERTICAL) {
                      return IndexedLayoutState.LAYOUT_END;
                  } else if (isLayoutRTL()) {
                      return IndexedLayoutState.LAYOUT_START;
                  } else {
                      return IndexedLayoutState.LAYOUT_END;
                  }
              case View.FOCUS_UP:
                  return mOrientation == VERTICAL ? IndexedLayoutState.LAYOUT_START
                          : IndexedLayoutState.INVALID_LAYOUT;
              case View.FOCUS_DOWN:
                  return mOrientation == VERTICAL ? IndexedLayoutState.LAYOUT_END
                          : IndexedLayoutState.INVALID_LAYOUT;
              case View.FOCUS_LEFT:
                  return mOrientation == HORIZONTAL ? IndexedLayoutState.LAYOUT_START
                          : IndexedLayoutState.INVALID_LAYOUT;
              case View.FOCUS_RIGHT:
                  return mOrientation == HORIZONTAL ? IndexedLayoutState.LAYOUT_END
                          : IndexedLayoutState.INVALID_LAYOUT;
              default:
                  if (DEBUG) {
                      Log.d(TAG, "Unknown focus request:" + focusDirection);
                  }
                  return IndexedLayoutState.INVALID_LAYOUT;
          }
  
      }
  
      /**
       * LayoutParams used by StaggeredGridLayoutManagerT.
       * <p>
       * Note that if the orientation is {@link #VERTICAL}, the width parameter is ignored and if the
       * orientation is {@link #HORIZONTAL} the height parameter is ignored because child view is
       * expected to fill all of the space given to it.
       */
      public static class LayoutParams extends RecyclerView.LayoutParams {
  
          /**
           * Span Id for Views that are not laid out yet.
           */
          public static final int INVALID_SPAN_ID = -1;
  
          // Package scope to be able to access from tests.
          FlapIndexedStaggeredGridLayoutManager.Span mSpan;
  
          boolean mFullSpan;
  
          public LayoutParams(Context c, AttributeSet attrs) {
              super(c, attrs);
          }
  
          public LayoutParams(int width, int height) {
              super(width, height);
          }
  
          public LayoutParams(ViewGroup.MarginLayoutParams source) {
              super(source);
          }
  
          public LayoutParams(ViewGroup.LayoutParams source) {
              super(source);
          }
  
          public LayoutParams(RecyclerView.LayoutParams source) {
              super(source);
          }
  
          /**
           * When set to true, the item will layout using all span area. That means, if orientation
           * is vertical, the view will have full width; if orientation is horizontal, the view will
           * have full height.
           *
           * @param fullSpan True if this item should traverse all spans.
           * @see #isFullSpan()
           */
          public void setFullSpan(boolean fullSpan) {
              mFullSpan = fullSpan;
          }
  
          /**
           * Returns whether this View occupies all available spans or just one.
           *
           * @return True if the View occupies all spans or false otherwise.
           * @see #setFullSpan(boolean)
           */
          public boolean isFullSpan() {
              return mFullSpan;
          }
  
          /**
           * Returns the Span index to which this View is assigned.
           *
           * @return The Span index of the View. If View is not yet assigned to any span, returns
           * {@link #INVALID_SPAN_ID}.
           */
          public final int getSpanIndex() {
              if (mSpan == null) {
                  return INVALID_SPAN_ID;
              }
              return mSpan.mIndex;
          }
      }
  
      // Package scoped to access from tests.
      class Span {
  
          static final int INVALID_LINE = Integer.MIN_VALUE;
          ArrayList<View> mViews = new ArrayList<>();
          int mCachedStart = INVALID_LINE;
          int mCachedEnd = INVALID_LINE;
          int mDeletedSize = 0;
          final int mIndex;
  
          Span(int index) {
              mIndex = index;
          }
  
          int getStartLine(int def) {
              if (mCachedStart != INVALID_LINE) {
                  return mCachedStart;
              }
              if (mViews.size() == 0) {
                  return def;
              }
              calculateCachedStart();
              return mCachedStart;
          }
  
          void calculateCachedStart() {
              // Fatal Exception: java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0
              // https://stackoverflow.com/questions/44571895/staggeredgridlayoutmanager-calculatecachedstart-indexoutofboundsexception
              // 解法: 设置 mGapStrategy == GAP_HANDLING_NONE
              // 这里主动规避一下 IndexOutOfBoundsException
              if (mViews.size() == 0) {
                  return;
              }
  
              final View startView = mViews.get(0);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(startView);
              mCachedStart = mPrimaryOrientation.getDecoratedStart(startView);
              if (lp.mFullSpan) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
                          .getFullSpanItem(lp.getViewLayoutPosition());
                  if (fsi != null && fsi.mGapDir == IndexedLayoutState.LAYOUT_START) {
                      mCachedStart -= fsi.getGapForSpan(mIndex);
                  }
              }
          }
  
          // Use this one when default value does not make sense and not having a value means a bug.
          int getStartLine() {
              if (mCachedStart != INVALID_LINE) {
                  return mCachedStart;
              }
              calculateCachedStart();
              return mCachedStart;
          }
  
          int getEndLine(int def) {
              if (mCachedEnd != INVALID_LINE) {
                  return mCachedEnd;
              }
              final int size = mViews.size();
              if (size == 0) {
                  return def;
              }
              calculateCachedEnd();
              return mCachedEnd;
          }
  
          void calculateCachedEnd() {
              final View endView = mViews.get(mViews.size() - 1);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(endView);
              mCachedEnd = mPrimaryOrientation.getDecoratedEnd(endView);
              if (lp.mFullSpan) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mLazySpanLookup
                          .getFullSpanItem(lp.getViewLayoutPosition());
                  if (fsi != null && fsi.mGapDir == IndexedLayoutState.LAYOUT_END) {
                      mCachedEnd += fsi.getGapForSpan(mIndex);
                  }
              }
          }
  
          // Use this one when default value does not make sense and not having a value means a bug.
          int getEndLine() {
              if (mCachedEnd != INVALID_LINE) {
                  return mCachedEnd;
              }
              calculateCachedEnd();
              return mCachedEnd;
          }
  
          void prependToSpan(View view) {
              FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(view);
              lp.mSpan = this;
              mViews.add(0, view);
              mCachedStart = INVALID_LINE;
              if (mViews.size() == 1) {
                  mCachedEnd = INVALID_LINE;
              }
              if (lp.isItemRemoved() || lp.isItemChanged()) {
                  mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
              }
          }
  
          void appendToSpan(View view) {
              FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(view);
              lp.mSpan = this;
              mViews.add(view);
              mCachedEnd = INVALID_LINE;
              if (mViews.size() == 1) {
                  mCachedStart = INVALID_LINE;
              }
              if (lp.isItemRemoved() || lp.isItemChanged()) {
                  mDeletedSize += mPrimaryOrientation.getDecoratedMeasurement(view);
              }
          }
  
          // Useful method to preserve positions on a re-layout.
          void cacheReferenceLineAndClear(boolean reverseLayout, int offset) {
              int reference;
              if (reverseLayout) {
                  reference = getEndLine(INVALID_LINE);
              } else {
                  reference = getStartLine(INVALID_LINE);
              }
              clear();
              if (reference == INVALID_LINE) {
                  return;
              }
              if ((reverseLayout && reference < mPrimaryOrientation.getEndAfterPadding())
                      || (!reverseLayout && reference > mPrimaryOrientation.getStartAfterPadding())) {
                  return;
              }
              if (offset != INVALID_OFFSET) {
                  reference += offset;
              }
              mCachedStart = mCachedEnd = reference;
          }
  
          void clear() {
              mViews.clear();
              invalidateCache();
              mDeletedSize = 0;
          }
  
          void invalidateCache() {
              mCachedStart = INVALID_LINE;
              mCachedEnd = INVALID_LINE;
          }
  
          void setLine(int line) {
              mCachedEnd = mCachedStart = line;
          }
  
          void popEnd() {
              final int size = mViews.size();
              View end = mViews.remove(size - 1);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(end);
              lp.mSpan = null;
              if (lp.isItemRemoved() || lp.isItemChanged()) {
                  mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(end);
              }
              if (size == 1) {
                  mCachedStart = INVALID_LINE;
              }
              mCachedEnd = INVALID_LINE;
          }
  
          void popStart() {
              View start = mViews.remove(0);
              final FlapIndexedStaggeredGridLayoutManager.LayoutParams lp = getLayoutParams(start);
              lp.mSpan = null;
              if (mViews.size() == 0) {
                  mCachedEnd = INVALID_LINE;
              }
              if (lp.isItemRemoved() || lp.isItemChanged()) {
                  mDeletedSize -= mPrimaryOrientation.getDecoratedMeasurement(start);
              }
              mCachedStart = INVALID_LINE;
          }
  
          public int getDeletedSize() {
              return mDeletedSize;
          }
  
          FlapIndexedStaggeredGridLayoutManager.LayoutParams getLayoutParams(View view) {
              return (FlapIndexedStaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
          }
  
          void onOffset(int dt) {
              if (mCachedStart != INVALID_LINE) {
                  mCachedStart += dt;
              }
              if (mCachedEnd != INVALID_LINE) {
                  mCachedEnd += dt;
              }
          }
  
          public int findFirstVisibleItemPosition() {
              return mReverseLayout
                      ? findOneVisibleChild(mViews.size() - 1, -1, false)
                      : findOneVisibleChild(0, mViews.size(), false);
          }
  
          public int findFirstPartiallyVisibleItemPosition() {
              return mReverseLayout
                      ? findOnePartiallyVisibleChild(mViews.size() - 1, -1, true)
                      : findOnePartiallyVisibleChild(0, mViews.size(), true);
          }
  
          public int findFirstCompletelyVisibleItemPosition() {
              return mReverseLayout
                      ? findOneVisibleChild(mViews.size() - 1, -1, true)
                      : findOneVisibleChild(0, mViews.size(), true);
          }
  
          public int findLastVisibleItemPosition() {
              return mReverseLayout
                      ? findOneVisibleChild(0, mViews.size(), false)
                      : findOneVisibleChild(mViews.size() - 1, -1, false);
          }
  
          public int findLastPartiallyVisibleItemPosition() {
              return mReverseLayout
                      ? findOnePartiallyVisibleChild(0, mViews.size(), true)
                      : findOnePartiallyVisibleChild(mViews.size() - 1, -1, true);
          }
  
          public int findLastCompletelyVisibleItemPosition() {
              return mReverseLayout
                      ? findOneVisibleChild(0, mViews.size(), true)
                      : findOneVisibleChild(mViews.size() - 1, -1, true);
          }
  
          /**
           * Returns the first view within this span that is partially or fully visible. Partially
           * visible refers to a view that overlaps but is not fully contained within RV's padded
           * bounded area. This view returned can be defined to have an area of overlap strictly
           * greater than zero if acceptEndPointInclusion is false. If true, the view's endpoint
           * inclusion is enough to consider it partially visible. The latter case can then refer to
           * an out-of-bounds view positioned right at the top (or bottom) boundaries of RV's padded
           * area. This is used e.g. inside
           * {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)} for
           * calculating the next unfocusable child to become visible on the screen.
           *
           * @param fromIndex               The child position index to start the search from.
           * @param toIndex                 The child position index to end the search at.
           * @param completelyVisible       True if we have to only consider completely visible views,
           *                                false otherwise.
           * @param acceptCompletelyVisible True if we can consider both partially or fully visible
           *                                views, false, if only a partially visible child should be
           *                                returned.
           * @param acceptEndPointInclusion If the view's endpoint intersection with RV's padded
           *                                bounded area is enough to consider it partially visible,
           *                                false otherwise
           * @return The adapter position of the first view that's either partially or fully visible.
           * {@link RecyclerView#NO_POSITION} if no such view is found.
           */
          int findOnePartiallyOrCompletelyVisibleChild(int fromIndex, int toIndex,
                                                       boolean completelyVisible,
                                                       boolean acceptCompletelyVisible,
                                                       boolean acceptEndPointInclusion) {
              final int start = mPrimaryOrientation.getStartAfterPadding();
              final int end = mPrimaryOrientation.getEndAfterPadding();
              final int next = toIndex > fromIndex ? 1 : -1;
              for (int i = fromIndex; i != toIndex; i += next) {
                  final View child = mViews.get(i);
                  final int childStart = mPrimaryOrientation.getDecoratedStart(child);
                  final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
                  boolean childStartInclusion = acceptEndPointInclusion ? (childStart <= end)
                          : (childStart < end);
                  boolean childEndInclusion = acceptEndPointInclusion ? (childEnd >= start)
                          : (childEnd > start);
                  if (childStartInclusion && childEndInclusion) {
                      if (completelyVisible && acceptCompletelyVisible) {
                          // the child has to be completely visible to be returned.
                          if (childStart >= start && childEnd <= end) {
                              return getPosition(child);
                          }
                      } else if (acceptCompletelyVisible) {
                          // can return either a partially or completely visible child.
                          return getPosition(child);
                      } else if (childStart < start || childEnd > end) {
                          // should return a partially visible child if exists and a completely
                          // visible child is not acceptable in this case.
                          return getPosition(child);
                      }
                  }
              }
              return RecyclerView.NO_POSITION;
          }
  
          int findOneVisibleChild(int fromIndex, int toIndex, boolean completelyVisible) {
              return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, completelyVisible,
                      true, false);
          }
  
          int findOnePartiallyVisibleChild(int fromIndex, int toIndex,
                                           boolean acceptEndPointInclusion) {
              return findOnePartiallyOrCompletelyVisibleChild(fromIndex, toIndex, false, false,
                      acceptEndPointInclusion);
          }
  
          /**
           * Depending on the layout direction, returns the View that is after the given position.
           */
          public View getFocusableViewAfter(int referenceChildPosition, int layoutDir) {
              View candidate = null;
              if (layoutDir == IndexedLayoutState.LAYOUT_START) {
                  final int limit = mViews.size();
                  for (int i = 0; i < limit; i++) {
                      final View view = mViews.get(i);
                      if ((mReverseLayout && getPosition(view) <= referenceChildPosition)
                              || (!mReverseLayout && getPosition(view) >= referenceChildPosition)) {
                          break;
                      }
                      if (view.hasFocusable()) {
                          candidate = view;
                      } else {
                          break;
                      }
                  }
              } else {
                  for (int i = mViews.size() - 1; i >= 0; i--) {
                      final View view = mViews.get(i);
                      if ((mReverseLayout && getPosition(view) >= referenceChildPosition)
                              || (!mReverseLayout && getPosition(view) <= referenceChildPosition)) {
                          break;
                      }
                      if (view.hasFocusable()) {
                          candidate = view;
                      } else {
                          break;
                      }
                  }
              }
              return candidate;
          }
      }
  
      /**
       * An array of mappings from adapter position to span.
       * This only grows when a write happens and it grows up to the size of the adapter.
       */
      static class LazySpanLookup {
  
          private static final int MIN_SIZE = 10;
          int[] mData;
          List<FullSpanItem> mFullSpanItems;
  
          /**
           * Invalidates everything after this position, including full span information
           */
          int forceInvalidateAfter(int position) {
              if (mFullSpanItems != null) {
                  for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
                      FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                      if (fsi.mPosition >= position) {
                          mFullSpanItems.remove(i);
                      }
                  }
              }
              return invalidateAfter(position);
          }
  
          /**
           * returns end position for invalidation.
           */
          int invalidateAfter(int position) {
              if (mData == null) {
                  return RecyclerView.NO_POSITION;
              }
              if (position >= mData.length) {
                  return RecyclerView.NO_POSITION;
              }
              int endPosition = invalidateFullSpansAfter(position);
              if (endPosition == RecyclerView.NO_POSITION) {
                  Arrays.fill(mData, position, mData.length, FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
                  return mData.length;
              } else {
                  // Just invalidate items in between `position` and the next full span item, or the
                  // end of the tracked spans in mData if it's not been lengthened yet.
                  final int invalidateToIndex = Math.min(endPosition + 1, mData.length);
                  Arrays.fill(mData, position, invalidateToIndex, FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
                  return invalidateToIndex;
              }
          }
  
          int getSpan(int position) {
              if (mData == null || position >= mData.length) {
                  return FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID;
              } else {
                  return mData[position];
              }
          }
  
          void setSpan(int position, FlapIndexedStaggeredGridLayoutManager.Span span) {
              ensureSize(position);
              mData[position] = span.mIndex;
          }
  
          int sizeForPosition(int position) {
              int len = mData.length;
              while (len <= position) {
                  len *= 2;
              }
              return len;
          }
  
          void ensureSize(int position) {
              if (mData == null) {
                  mData = new int[Math.max(position, MIN_SIZE) + 1];
                  Arrays.fill(mData, FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
              } else if (position >= mData.length) {
                  int[] old = mData;
                  mData = new int[sizeForPosition(position)];
                  System.arraycopy(old, 0, mData, 0, old.length);
                  Arrays.fill(mData, old.length, mData.length, FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
              }
          }
  
          void clear() {
              if (mData != null) {
                  Arrays.fill(mData, FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
              }
              mFullSpanItems = null;
          }
  
          void offsetForRemoval(int positionStart, int itemCount) {
              if (mData == null || positionStart >= mData.length) {
                  return;
              }
              ensureSize(positionStart + itemCount);
              System.arraycopy(mData, positionStart + itemCount, mData, positionStart,
                      mData.length - positionStart - itemCount);
              Arrays.fill(mData, mData.length - itemCount, mData.length,
                      FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
              offsetFullSpansForRemoval(positionStart, itemCount);
          }
  
          private void offsetFullSpansForRemoval(int positionStart, int itemCount) {
              if (mFullSpanItems == null) {
                  return;
              }
              final int end = positionStart + itemCount;
              for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                  if (fsi.mPosition < positionStart) {
                      continue;
                  }
                  if (fsi.mPosition < end) {
                      mFullSpanItems.remove(i);
                  } else {
                      fsi.mPosition -= itemCount;
                  }
              }
          }
  
          void offsetForAddition(int positionStart, int itemCount) {
              if (mData == null || positionStart >= mData.length) {
                  return;
              }
              ensureSize(positionStart + itemCount);
              System.arraycopy(mData, positionStart, mData, positionStart + itemCount,
                      mData.length - positionStart - itemCount);
              Arrays.fill(mData, positionStart, positionStart + itemCount,
                      FlapIndexedStaggeredGridLayoutManager.LayoutParams.INVALID_SPAN_ID);
              offsetFullSpansForAddition(positionStart, itemCount);
          }
  
          private void offsetFullSpansForAddition(int positionStart, int itemCount) {
              if (mFullSpanItems == null) {
                  return;
              }
              for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                  if (fsi.mPosition < positionStart) {
                      continue;
                  }
                  fsi.mPosition += itemCount;
              }
          }
  
          /**
           * Returns when invalidation should end. e.g. hitting a full span position.
           * Returned position SHOULD BE invalidated.
           */
          private int invalidateFullSpansAfter(int position) {
              if (mFullSpanItems == null) {
                  return RecyclerView.NO_POSITION;
              }
              final FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem item = getFullSpanItem(position);
              // if there is an fsi at this position, get rid of it.
              if (item != null) {
                  mFullSpanItems.remove(item);
              }
              int nextFsiIndex = -1;
              final int count = mFullSpanItems.size();
              for (int i = 0; i < count; i++) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                  if (fsi.mPosition >= position) {
                      nextFsiIndex = i;
                      break;
                  }
              }
              if (nextFsiIndex != -1) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(nextFsiIndex);
                  mFullSpanItems.remove(nextFsiIndex);
                  return fsi.mPosition;
              }
              return RecyclerView.NO_POSITION;
          }
  
          public void addFullSpanItem(FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fullSpanItem) {
              if (mFullSpanItems == null) {
                  mFullSpanItems = new ArrayList<>();
              }
              final int size = mFullSpanItems.size();
              for (int i = 0; i < size; i++) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem other = mFullSpanItems.get(i);
                  if (other.mPosition == fullSpanItem.mPosition) {
                      if (DEBUG) {
                          throw new IllegalStateException("two fsis for same position");
                      } else {
                          mFullSpanItems.remove(i);
                      }
                  }
                  if (other.mPosition >= fullSpanItem.mPosition) {
                      mFullSpanItems.add(i, fullSpanItem);
                      return;
                  }
              }
              // if it is not added to a position.
              mFullSpanItems.add(fullSpanItem);
          }
  
          public FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem getFullSpanItem(int position) {
              if (mFullSpanItems == null) {
                  return null;
              }
              for (int i = mFullSpanItems.size() - 1; i >= 0; i--) {
                  final FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                  if (fsi.mPosition == position) {
                      return fsi;
                  }
              }
              return null;
          }
  
          /**
           * @param minPos              inclusive
           * @param maxPos              exclusive
           * @param gapDir              if not 0, returns FSIs on in that direction
           * @param hasUnwantedGapAfter If true, when full span item has unwanted gaps, it will be
           *                            returned even if its gap direction does not match.
           */
          public FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem getFirstFullSpanItemInRange(int minPos, int maxPos, int gapDir,
                                                                                                               boolean hasUnwantedGapAfter) {
              if (mFullSpanItems == null) {
                  return null;
              }
              final int limit = mFullSpanItems.size();
              for (int i = 0; i < limit; i++) {
                  FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem fsi = mFullSpanItems.get(i);
                  if (fsi.mPosition >= maxPos) {
                      return null;
                  }
                  if (fsi.mPosition >= minPos
                          && (gapDir == 0 || fsi.mGapDir == gapDir
                          || (hasUnwantedGapAfter && fsi.mHasUnwantedGapAfter))) {
                      return fsi;
                  }
              }
              return null;
          }
  
          /**
           * We keep information about full span items because they may create gaps in the UI.
           */
          @SuppressLint("BanParcelableUsage")
          static class FullSpanItem implements Parcelable {
  
              int mPosition;
              int mGapDir;
              int[] mGapPerSpan;
              // A full span may be laid out in primary direction but may have gaps due to
              // invalidation of views after it. This is recorded during a reverse scroll and if
              // view is still on the screen after scroll stops, we have to recalculate layout
              boolean mHasUnwantedGapAfter;
  
              FullSpanItem(Parcel in) {
                  mPosition = in.readInt();
                  mGapDir = in.readInt();
                  mHasUnwantedGapAfter = in.readInt() == 1;
                  int spanCount = in.readInt();
                  if (spanCount > 0) {
                      mGapPerSpan = new int[spanCount];
                      in.readIntArray(mGapPerSpan);
                  }
              }
  
              FullSpanItem() {
              }
  
              int getGapForSpan(int spanIndex) {
                  return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
              }
  
              @Override
              public int describeContents() {
                  return 0;
              }
  
              @Override
              public void writeToParcel(Parcel dest, int flags) {
                  dest.writeInt(mPosition);
                  dest.writeInt(mGapDir);
                  dest.writeInt(mHasUnwantedGapAfter ? 1 : 0);
                  if (mGapPerSpan != null && mGapPerSpan.length > 0) {
                      dest.writeInt(mGapPerSpan.length);
                      dest.writeIntArray(mGapPerSpan);
                  } else {
                      dest.writeInt(0);
                  }
              }
  
              @Override
              public String toString() {
                  return "FullSpanItem{"
                          + "mPosition=" + mPosition
                          + ", mGapDir=" + mGapDir
                          + ", mHasUnwantedGapAfter=" + mHasUnwantedGapAfter
                          + ", mGapPerSpan=" + Arrays.toString(mGapPerSpan)
                          + '}';
              }
  
              public static final Parcelable.Creator<FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem> CREATOR =
                      new Parcelable.Creator<FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem>() {
                          @Override
                          public FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem createFromParcel(Parcel in) {
                              return new FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem(in);
                          }
  
                          @Override
                          public FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem[] newArray(int size) {
                              return new FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem[size];
                          }
                      };
          }
      }
  
      /**
       * @hide
       */
      @RestrictTo(LIBRARY)
      @SuppressLint("BanParcelableUsage")
      public static class SavedState implements Parcelable {
  
          int mAnchorPosition;
          int mVisibleAnchorPosition; // Replacement for span info when spans are invalidated
          int mSpanOffsetsSize;
          int[] mSpanOffsets;
          int mSpanLookupSize;
          int[] mSpanLookup;
          List<FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem> mFullSpanItems;
          boolean mReverseLayout;
          boolean mAnchorLayoutFromEnd;
          boolean mLastLayoutRTL;
  
          public SavedState() {
          }
  
          SavedState(Parcel in) {
              mAnchorPosition = in.readInt();
              mVisibleAnchorPosition = in.readInt();
              mSpanOffsetsSize = in.readInt();
              if (mSpanOffsetsSize > 0) {
                  mSpanOffsets = new int[mSpanOffsetsSize];
                  in.readIntArray(mSpanOffsets);
              }
  
              mSpanLookupSize = in.readInt();
              if (mSpanLookupSize > 0) {
                  mSpanLookup = new int[mSpanLookupSize];
                  in.readIntArray(mSpanLookup);
              }
              mReverseLayout = in.readInt() == 1;
              mAnchorLayoutFromEnd = in.readInt() == 1;
              mLastLayoutRTL = in.readInt() == 1;
              @SuppressWarnings("unchecked")
              List<FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem> fullSpanItems =
                      in.readArrayList(FlapIndexedStaggeredGridLayoutManager.LazySpanLookup.FullSpanItem.class.getClassLoader());
              mFullSpanItems = fullSpanItems;
          }
  
          public SavedState(FlapIndexedStaggeredGridLayoutManager.SavedState other) {
              mSpanOffsetsSize = other.mSpanOffsetsSize;
              mAnchorPosition = other.mAnchorPosition;
              mVisibleAnchorPosition = other.mVisibleAnchorPosition;
              mSpanOffsets = other.mSpanOffsets;
              mSpanLookupSize = other.mSpanLookupSize;
              mSpanLookup = other.mSpanLookup;
              mReverseLayout = other.mReverseLayout;
              mAnchorLayoutFromEnd = other.mAnchorLayoutFromEnd;
              mLastLayoutRTL = other.mLastLayoutRTL;
              mFullSpanItems = other.mFullSpanItems;
          }
  
          void invalidateSpanInfo() {
              mSpanOffsets = null;
              mSpanOffsetsSize = 0;
              mSpanLookupSize = 0;
              mSpanLookup = null;
              mFullSpanItems = null;
          }
  
          void invalidateAnchorPositionInfo() {
              mSpanOffsets = null;
              mSpanOffsetsSize = 0;
              mAnchorPosition = RecyclerView.NO_POSITION;
              mVisibleAnchorPosition = RecyclerView.NO_POSITION;
          }
  
          @Override
          public int describeContents() {
              return 0;
          }
  
          @Override
          public void writeToParcel(Parcel dest, int flags) {
              dest.writeInt(mAnchorPosition);
              dest.writeInt(mVisibleAnchorPosition);
              dest.writeInt(mSpanOffsetsSize);
              if (mSpanOffsetsSize > 0) {
                  dest.writeIntArray(mSpanOffsets);
              }
              dest.writeInt(mSpanLookupSize);
              if (mSpanLookupSize > 0) {
                  dest.writeIntArray(mSpanLookup);
              }
              dest.writeInt(mReverseLayout ? 1 : 0);
              dest.writeInt(mAnchorLayoutFromEnd ? 1 : 0);
              dest.writeInt(mLastLayoutRTL ? 1 : 0);
              dest.writeList(mFullSpanItems);
          }
  
          public static final Parcelable.Creator<FlapIndexedStaggeredGridLayoutManager.SavedState> CREATOR =
                  new Parcelable.Creator<FlapIndexedStaggeredGridLayoutManager.SavedState>() {
                      @Override
                      public FlapIndexedStaggeredGridLayoutManager.SavedState createFromParcel(Parcel in) {
                          return new FlapIndexedStaggeredGridLayoutManager.SavedState(in);
                      }
  
                      @Override
                      public FlapIndexedStaggeredGridLayoutManager.SavedState[] newArray(int size) {
                          return new FlapIndexedStaggeredGridLayoutManager.SavedState[size];
                      }
                  };
      }
  
      /**
       * Data class to hold the information about an anchor position which is used in onLayout call.
       */
      class AnchorInfo {
  
          int mPosition;
          int mOffset;
          boolean mLayoutFromEnd;
          boolean mInvalidateOffsets;
          boolean mValid;
          // this is where we save span reference lines in case we need to re-use them for multi-pass
          // measure steps
          int[] mSpanReferenceLines;
  
          AnchorInfo() {
              reset();
          }
  
          void reset() {
              mPosition = RecyclerView.NO_POSITION;
              mOffset = INVALID_OFFSET;
              mLayoutFromEnd = false;
              mInvalidateOffsets = false;
              mValid = false;
              if (mSpanReferenceLines != null) {
                  Arrays.fill(mSpanReferenceLines, -1);
              }
          }
  
          void saveSpanReferenceLines(FlapIndexedStaggeredGridLayoutManager.Span[] spans) {
              int spanCount = spans.length;
              if (mSpanReferenceLines == null || mSpanReferenceLines.length < spanCount) {
                  mSpanReferenceLines = new int[mSpans.length];
              }
              for (int i = 0; i < spanCount; i++) {
                  // does not matter start or end since this is only recorded when span is reset
                  mSpanReferenceLines[i] = spans[i].getStartLine(FlapIndexedStaggeredGridLayoutManager.Span.INVALID_LINE);
              }
          }
  
          void assignCoordinateFromPadding() {
              mOffset = mLayoutFromEnd ? mPrimaryOrientation.getEndAfterPadding()
                      : mPrimaryOrientation.getStartAfterPadding();
          }
  
          void assignCoordinateFromPadding(int addedDistance) {
              if (mLayoutFromEnd) {
                  mOffset = mPrimaryOrientation.getEndAfterPadding() - addedDistance;
              } else {
                  mOffset = mPrimaryOrientation.getStartAfterPadding() + addedDistance;
              }
          }
      }
  }
  