Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
      5  * in compliance with the License. You may obtain a copy of the License at
      6  *
      7  * http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the License
     10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
     11  * or implied. See the License for the specific language governing permissions and limitations under
     12  * the License.
     13  */
     14 package androidx.leanback.widget;
     15 
     16 import static androidx.recyclerview.widget.RecyclerView.HORIZONTAL;
     17 import static androidx.recyclerview.widget.RecyclerView.NO_ID;
     18 import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
     19 import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
     20 import static androidx.recyclerview.widget.RecyclerView.VERTICAL;
     21 
     22 import android.content.Context;
     23 import android.graphics.PointF;
     24 import android.graphics.Rect;
     25 import android.os.Build;
     26 import android.os.Bundle;
     27 import android.os.Parcel;
     28 import android.os.Parcelable;
     29 import android.util.AttributeSet;
     30 import android.util.Log;
     31 import android.util.SparseIntArray;
     32 import android.view.FocusFinder;
     33 import android.view.Gravity;
     34 import android.view.View;
     35 import android.view.View.MeasureSpec;
     36 import android.view.ViewGroup;
     37 import android.view.ViewGroup.MarginLayoutParams;
     38 import android.view.animation.AccelerateDecelerateInterpolator;
     39 
     40 import androidx.annotation.VisibleForTesting;
     41 import androidx.collection.CircularIntArray;
     42 import androidx.core.os.TraceCompat;
     43 import androidx.core.view.ViewCompat;
     44 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
     45 import androidx.recyclerview.widget.LinearSmoothScroller;
     46 import androidx.recyclerview.widget.OrientationHelper;
     47 import androidx.recyclerview.widget.RecyclerView;
     48 import androidx.recyclerview.widget.RecyclerView.Recycler;
     49 import androidx.recyclerview.widget.RecyclerView.State;
     50 
     51 import java.io.PrintWriter;
     52 import java.io.StringWriter;
     53 import java.util.ArrayList;
     54 import java.util.Arrays;
     55 import java.util.List;
     56 
     57 final class GridLayoutManager extends RecyclerView.LayoutManager {
     58 
     59     /*
     60      * LayoutParams for {@link HorizontalGridView} and {@link VerticalGridView}.
     61      * The class currently does two internal jobs:
     62      * - Saves optical bounds insets.
     63      * - Caches focus align view center.
     64      */
     65     final static class LayoutParams extends RecyclerView.LayoutParams {
     66 
     67         // For placement
     68         int mLeftInset;
     69         int mTopInset;
     70         int mRightInset;
     71         int mBottomInset;
     72 
     73         // For alignment
     74         private int mAlignX;
     75         private int mAlignY;
     76         private int[] mAlignMultiple;
     77         private ItemAlignmentFacet mAlignmentFacet;
     78 
     79         public LayoutParams(Context c, AttributeSet attrs) {
     80             super(c, attrs);
     81         }
     82 
     83         public LayoutParams(int width, int height) {
     84             super(width, height);
     85         }
     86 
     87         public LayoutParams(MarginLayoutParams source) {
     88             super(source);
     89         }
     90 
     91         public LayoutParams(ViewGroup.LayoutParams source) {
     92             super(source);
     93         }
     94 
     95         public LayoutParams(RecyclerView.LayoutParams source) {
     96             super(source);
     97         }
     98 
     99         public LayoutParams(LayoutParams source) {
    100             super(source);
    101         }
    102 
    103         int getAlignX() {
    104             return mAlignX;
    105         }
    106 
    107         int getAlignY() {
    108             return mAlignY;
    109         }
    110 
    111         int getOpticalLeft(View view) {
    112             return view.getLeft() + mLeftInset;
    113         }
    114 
    115         int getOpticalTop(View view) {
    116             return view.getTop() + mTopInset;
    117         }
    118 
    119         int getOpticalRight(View view) {
    120             return view.getRight() - mRightInset;
    121         }
    122 
    123         int getOpticalBottom(View view) {
    124             return view.getBottom() - mBottomInset;
    125         }
    126 
    127         int getOpticalWidth(View view) {
    128             return view.getWidth() - mLeftInset - mRightInset;
    129         }
    130 
    131         int getOpticalHeight(View view) {
    132             return view.getHeight() - mTopInset - mBottomInset;
    133         }
    134 
    135         int getOpticalLeftInset() {
    136             return mLeftInset;
    137         }
    138 
    139         int getOpticalRightInset() {
    140             return mRightInset;
    141         }
    142 
    143         int getOpticalTopInset() {
    144             return mTopInset;
    145         }
    146 
    147         int getOpticalBottomInset() {
    148             return mBottomInset;
    149         }
    150 
    151         void setAlignX(int alignX) {
    152             mAlignX = alignX;
    153         }
    154 
    155         void setAlignY(int alignY) {
    156             mAlignY = alignY;
    157         }
    158 
    159         void setItemAlignmentFacet(ItemAlignmentFacet facet) {
    160             mAlignmentFacet = facet;
    161         }
    162 
    163         ItemAlignmentFacet getItemAlignmentFacet() {
    164             return mAlignmentFacet;
    165         }
    166 
    167         void calculateItemAlignments(int orientation, View view) {
    168             ItemAlignmentFacet.ItemAlignmentDef[] defs = mAlignmentFacet.getAlignmentDefs();
    169             if (mAlignMultiple == null || mAlignMultiple.length != defs.length) {
    170                 mAlignMultiple = new int[defs.length];
    171             }
    172             for (int i = 0; i < defs.length; i++) {
    173                 mAlignMultiple[i] = ItemAlignmentFacetHelper
    174                         .getAlignmentPosition(view, defs[i], orientation);
    175             }
    176             if (orientation == HORIZONTAL) {
    177                 mAlignX = mAlignMultiple[0];
    178             } else {
    179                 mAlignY = mAlignMultiple[0];
    180             }
    181         }
    182 
    183         int[] getAlignMultiple() {
    184             return mAlignMultiple;
    185         }
    186 
    187         void setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset) {
    188             mLeftInset = leftInset;
    189             mTopInset = topInset;
    190             mRightInset = rightInset;
    191             mBottomInset = bottomInset;
    192         }
    193 
    194     }
    195 
    196     /**
    197      * Base class which scrolls to selected view in onStop().
    198      */
    199     abstract class GridLinearSmoothScroller extends LinearSmoothScroller {
    200         boolean mSkipOnStopInternal;
    201 
    202         GridLinearSmoothScroller() {
    203             super(mBaseGridView.getContext());
    204         }
    205 
    206         @Override
    207         protected void onStop() {
    208             super.onStop();
    209             if (!mSkipOnStopInternal) {
    210                 onStopInternal();
    211             }
    212             if (mCurrentSmoothScroller == this) {
    213                 mCurrentSmoothScroller = null;
    214             }
    215             if (mPendingMoveSmoothScroller == this) {
    216                 mPendingMoveSmoothScroller = null;
    217             }
    218         }
    219 
    220         protected void onStopInternal() {
    221             // onTargetFound() may not be called if we hit the "wall" first or get cancelled.
    222             View targetView = findViewByPosition(getTargetPosition());
    223             if (targetView == null) {
    224                 if (getTargetPosition() >= 0) {
    225                     // if smooth scroller is stopped without target, immediately jumps
    226                     // to the target position.
    227                     scrollToSelection(getTargetPosition(), 0, false, 0);
    228                 }
    229                 return;
    230             }
    231             if (mFocusPosition != getTargetPosition()) {
    232                 // This should not happen since we cropped value in startPositionSmoothScroller()
    233                 mFocusPosition = getTargetPosition();
    234             }
    235             if (hasFocus()) {
    236                 mFlag |= PF_IN_SELECTION;
    237                 targetView.requestFocus();
    238                 mFlag &= ~PF_IN_SELECTION;
    239             }
    240             dispatchChildSelected();
    241             dispatchChildSelectedAndPositioned();
    242         }
    243 
    244         @Override
    245         protected int calculateTimeForScrolling(int dx) {
    246             int ms = super.calculateTimeForScrolling(dx);
    247             if (mWindowAlignment.mainAxis().getSize() > 0) {
    248                 float minMs = (float) MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN
    249                         / mWindowAlignment.mainAxis().getSize() * dx;
    250                 if (ms < minMs) {
    251                     ms = (int) minMs;
    252                 }
    253             }
    254             return ms;
    255         }
    256 
    257         @Override
    258         protected void onTargetFound(View targetView,
    259                 RecyclerView.State state, Action action) {
    260             if (getScrollPosition(targetView, null, sTwoInts)) {
    261                 int dx, dy;
    262                 if (mOrientation == HORIZONTAL) {
    263                     dx = sTwoInts[0];
    264                     dy = sTwoInts[1];
    265                 } else {
    266                     dx = sTwoInts[1];
    267                     dy = sTwoInts[0];
    268                 }
    269                 final int distance = (int) Math.sqrt(dx * dx + dy * dy);
    270                 final int time = calculateTimeForDeceleration(distance);
    271                 action.update(dx, dy, time, mDecelerateInterpolator);
    272             }
    273         }
    274     }
    275 
    276     /**
    277      * The SmoothScroller that remembers pending DPAD keys and consume pending keys
    278      * during scroll.
    279      */
    280     final class PendingMoveSmoothScroller extends GridLinearSmoothScroller {
    281         // -2 is a target position that LinearSmoothScroller can never find until
    282         // consumePendingMovesXXX() sets real targetPosition.
    283         final static int TARGET_UNDEFINED = -2;
    284         // whether the grid is staggered.
    285         private final boolean mStaggeredGrid;
    286         // Number of pending movements on primary direction, negative if PREV_ITEM.
    287         private int mPendingMoves;
    288 
    289         PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
    290             mPendingMoves = initialPendingMoves;
    291             mStaggeredGrid = staggeredGrid;
    292             setTargetPosition(TARGET_UNDEFINED);
    293         }
    294 
    295         void increasePendingMoves() {
    296             if (mPendingMoves < mMaxPendingMoves) {
    297                 mPendingMoves++;
    298             }
    299         }
    300 
    301         void decreasePendingMoves() {
    302             if (mPendingMoves > -mMaxPendingMoves) {
    303                 mPendingMoves--;
    304             }
    305         }
    306 
    307         /**
    308          * Called before laid out an item when non-staggered grid can handle pending movements
    309          * by skipping "mNumRows" per movement;  staggered grid will have to wait the item
    310          * has been laid out in consumePendingMovesAfterLayout().
    311          */
    312         void consumePendingMovesBeforeLayout() {
    313             if (mStaggeredGrid || mPendingMoves == 0) {
    314                 return;
    315             }
    316             View newSelected = null;
    317             int startPos = mPendingMoves > 0 ? mFocusPosition + mNumRows :
    318                     mFocusPosition - mNumRows;
    319             for (int pos = startPos; mPendingMoves != 0;
    320                     pos = mPendingMoves > 0 ? pos + mNumRows: pos - mNumRows) {
    321                 View v = findViewByPosition(pos);
    322                 if (v == null) {
    323                     break;
    324                 }
    325                 if (!canScrollTo(v)) {
    326                     continue;
    327                 }
    328                 newSelected = v;
    329                 mFocusPosition = pos;
    330                 mSubFocusPosition = 0;
    331                 if (mPendingMoves > 0) {
    332                     mPendingMoves--;
    333                 } else {
    334                     mPendingMoves++;
    335                 }
    336             }
    337             if (newSelected != null && hasFocus()) {
    338                 mFlag |= PF_IN_SELECTION;
    339                 newSelected.requestFocus();
    340                 mFlag &= ~PF_IN_SELECTION;
    341             }
    342         }
    343 
    344         /**
    345          * Called after laid out an item.  Staggered grid should find view on same
    346          * Row and consume pending movements.
    347          */
    348         void consumePendingMovesAfterLayout() {
    349             if (mStaggeredGrid && mPendingMoves != 0) {
    350                 // consume pending moves, focus to item on the same row.
    351                 mPendingMoves = processSelectionMoves(true, mPendingMoves);
    352             }
    353             if (mPendingMoves == 0 || (mPendingMoves > 0 && hasCreatedLastItem())
    354                     || (mPendingMoves < 0 && hasCreatedFirstItem())) {
    355                 setTargetPosition(mFocusPosition);
    356                 stop();
    357             }
    358         }
    359 
    360         @Override
    361         protected void updateActionForInterimTarget(Action action) {
    362             if (mPendingMoves == 0) {
    363                 return;
    364             }
    365             super.updateActionForInterimTarget(action);
    366         }
    367 
    368         @Override
    369         public PointF computeScrollVectorForPosition(int targetPosition) {
    370             if (mPendingMoves == 0) {
    371                 return null;
    372             }
    373             int direction = ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
    374                     ? mPendingMoves > 0 : mPendingMoves < 0)
    375                     ? -1 : 1;
    376             if (mOrientation == HORIZONTAL) {
    377                 return new PointF(direction, 0);
    378             } else {
    379                 return new PointF(0, direction);
    380             }
    381         }
    382 
    383         @Override
    384         protected void onStopInternal() {
    385             super.onStopInternal();
    386             // if we hit wall,  need clear the remaining pending moves.
    387             mPendingMoves = 0;
    388             View v = findViewByPosition(getTargetPosition());
    389             if (v != null) scrollToView(v, true);
    390         }
    391     };
    392 
    393     private static final String TAG = "GridLayoutManager";
    394     static final boolean DEBUG = false;
    395     static final boolean TRACE = false;
    396 
    397     // maximum pending movement in one direction.
    398     static final int DEFAULT_MAX_PENDING_MOVES = 10;
    399     int mMaxPendingMoves = DEFAULT_MAX_PENDING_MOVES;
    400     // minimal milliseconds to scroll window size in major direction,  we put a cap to prevent the
    401     // effect smooth scrolling too over to bind an item view then drag the item view back.
    402     final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
    403 
    404     String getTag() {
    405         return TAG + ":" + mBaseGridView.getId();
    406     }
    407 
    408     final BaseGridView mBaseGridView;
    409 
    410     /**
    411      * Note on conventions in the presence of RTL layout directions:
    412      * Many properties and method names reference entities related to the
    413      * beginnings and ends of things.  In the presence of RTL flows,
    414      * it may not be clear whether this is intended to reference a
    415      * quantity that changes direction in RTL cases, or a quantity that
    416      * does not.  Here are the conventions in use:
    417      *
    418      * start/end: coordinate quantities - do reverse
    419      * (optical) left/right: coordinate quantities - do not reverse
    420      * low/high: coordinate quantities - do not reverse
    421      * min/max: coordinate quantities - do not reverse
    422      * scroll offset - coordinate quantities - do not reverse
    423      * first/last: positional indices - do not reverse
    424      * front/end: positional indices - do not reverse
    425      * prepend/append: related to positional indices - do not reverse
    426      *
    427      * Note that although quantities do not reverse in RTL flows, their
    428      * relationship does.  In LTR flows, the first positional index is
    429      * leftmost; in RTL flows, it is rightmost.  Thus, anywhere that
    430      * positional quantities are mapped onto coordinate quantities,
    431      * the flow must be checked and the logic reversed.
    432      */
    433 
    434     /**
    435      * The orientation of a "row".
    436      */
    437     @RecyclerView.Orientation
    438     int mOrientation = HORIZONTAL;
    439     private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
    440 
    441     RecyclerView.State mState;
    442     // Suppose currently showing 4, 5, 6, 7; removing 2,3,4 will make the layoutPosition to be
    443     // 2(deleted), 3, 4, 5 in prelayout pass. So when we add item in prelayout, we must subtract 2
    444     // from index of Grid.createItem.
    445     int mPositionDeltaInPreLayout;
    446     // Extra layout space needs to fill in prelayout pass. Note we apply the extra space to both
    447     // appends and prepends due to the fact leanback is doing mario scrolling: removing items to
    448     // the left of focused item might need extra layout on the right.
    449     int mExtraLayoutSpaceInPreLayout;
    450     // mPositionToRowInPostLayout and mDisappearingPositions are temp variables in post layout.
    451     final SparseIntArray mPositionToRowInPostLayout = new SparseIntArray();
    452     int[] mDisappearingPositions;
    453 
    454     RecyclerView.Recycler mRecycler;
    455 
    456     private static final Rect sTempRect = new Rect();
    457 
    458     // 2 bits mask is for 3 STAGEs: 0, PF_STAGE_LAYOUT or PF_STAGE_SCROLL.
    459     static final int PF_STAGE_MASK = 0x3;
    460     static final int PF_STAGE_LAYOUT = 0x1;
    461     static final int PF_STAGE_SCROLL = 0x2;
    462 
    463     // Flag for "in fast relayout", determined by layoutInit() result.
    464     static final int PF_FAST_RELAYOUT = 1 << 2;
    465 
    466     // Flag for the selected item being updated in fast relayout.
    467     static final int PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION = 1 << 3;
    468     /**
    469      * During full layout pass, when GridView had focus: onLayoutChildren will
    470      * skip non-focusable child and adjust mFocusPosition.
    471      */
    472     static final int PF_IN_LAYOUT_SEARCH_FOCUS = 1 << 4;
    473 
    474     // flag to prevent reentry if it's already processing selection request.
    475     static final int PF_IN_SELECTION = 1 << 5;
    476 
    477     // Represents whether child views are temporarily sliding out
    478     static final int PF_SLIDING = 1 << 6;
    479     static final int PF_LAYOUT_EATEN_IN_SLIDING = 1 << 7;
    480 
    481     /**
    482      * Force a full layout under certain situations.  E.g. Rows change, jump to invisible child.
    483      */
    484     static final int PF_FORCE_FULL_LAYOUT = 1 << 8;
    485 
    486     /**
    487      * True if layout is enabled.
    488      */
    489     static final int PF_LAYOUT_ENABLED = 1 << 9;
    490 
    491     /**
    492      * Flag controlling whether the current/next layout should
    493      * be updating the secondary size of rows.
    494      */
    495     static final int PF_ROW_SECONDARY_SIZE_REFRESH = 1 << 10;
    496 
    497     /**
    498      *  Allow DPAD key to navigate out at the front of the View (where position = 0),
    499      *  default is false.
    500      */
    501     static final int PF_FOCUS_OUT_FRONT = 1 << 11;
    502 
    503     /**
    504      * Allow DPAD key to navigate out at the end of the view, default is false.
    505      */
    506     static final int PF_FOCUS_OUT_END = 1 << 12;
    507 
    508     static final int PF_FOCUS_OUT_MASKS = PF_FOCUS_OUT_FRONT | PF_FOCUS_OUT_END;
    509 
    510     /**
    511      *  Allow DPAD key to navigate out of second axis.
    512      *  default is true.
    513      */
    514     static final int PF_FOCUS_OUT_SIDE_START = 1 << 13;
    515 
    516     /**
    517      * Allow DPAD key to navigate out of second axis.
    518      */
    519     static final int PF_FOCUS_OUT_SIDE_END = 1 << 14;
    520 
    521     static final int PF_FOCUS_OUT_SIDE_MASKS = PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END;
    522 
    523     /**
    524      * True if focus search is disabled.
    525      */
    526     static final int PF_FOCUS_SEARCH_DISABLED = 1 << 15;
    527 
    528     /**
    529      * True if prune child,  might be disabled during transition.
    530      */
    531     static final int PF_PRUNE_CHILD = 1 << 16;
    532 
    533     /**
    534      * True if scroll content,  might be disabled during transition.
    535      */
    536     static final int PF_SCROLL_ENABLED = 1 << 17;
    537 
    538     /**
    539      * Set to true for RTL layout in horizontal orientation
    540      */
    541     static final int PF_REVERSE_FLOW_PRIMARY = 1 << 18;
    542 
    543     /**
    544      * Set to true for RTL layout in vertical orientation
    545      */
    546     static final int PF_REVERSE_FLOW_SECONDARY = 1 << 19;
    547 
    548     static final int PF_REVERSE_FLOW_MASK = PF_REVERSE_FLOW_PRIMARY | PF_REVERSE_FLOW_SECONDARY;
    549 
    550     int mFlag = PF_LAYOUT_ENABLED
    551             | PF_FOCUS_OUT_SIDE_START | PF_FOCUS_OUT_SIDE_END
    552             | PF_PRUNE_CHILD | PF_SCROLL_ENABLED;
    553 
    554     private OnChildSelectedListener mChildSelectedListener = null;
    555 
    556     private ArrayList<OnChildViewHolderSelectedListener> mChildViewHolderSelectedListeners = null;
    557 
    558     OnChildLaidOutListener mChildLaidOutListener = null;
    559 
    560     /**
    561      * The focused position, it's not the currently visually aligned position
    562      * but it is the final position that we intend to focus on. If there are
    563      * multiple setSelection() called, mFocusPosition saves last value.
    564      */
    565     int mFocusPosition = NO_POSITION;
    566 
    567     /**
    568      * A view can have multiple alignment position,  this is the index of which
    569      * alignment is used,  by default is 0.
    570      */
    571     int mSubFocusPosition = 0;
    572 
    573     /**
    574      * Current running SmoothScroller.
    575      */
    576     GridLinearSmoothScroller mCurrentSmoothScroller;
    577 
    578     /**
    579      * LinearSmoothScroller that consume pending DPAD movements. Can be same object as
    580      * mCurrentSmoothScroller when mCurrentSmoothScroller is PendingMoveSmoothScroller.
    581      */
    582     PendingMoveSmoothScroller mPendingMoveSmoothScroller;
    583 
    584     /**
    585      * The offset to be applied to mFocusPosition, due to adapter change, on the next
    586      * layout.  Set to Integer.MIN_VALUE means we should stop adding delta to mFocusPosition
    587      * until next layout cycler.
    588      * TODO:  This is somewhat duplication of RecyclerView getOldPosition() which is
    589      * unfortunately cleared after prelayout.
    590      */
    591     private int mFocusPositionOffset = 0;
    592 
    593     /**
    594      * Extra pixels applied on primary direction.
    595      */
    596     private int mPrimaryScrollExtra;
    597 
    598     /**
    599      * override child visibility
    600      */
    601     @Visibility
    602     int mChildVisibility;
    603 
    604     /**
    605      * Pixels that scrolled in secondary forward direction. Negative value means backward.
    606      * Note that we treat secondary differently than main. For the main axis, update scroll min/max
    607      * based on first/last item's view location. For second axis, we don't use item's view location.
    608      * We are using the {@link #getRowSizeSecondary(int)} plus mScrollOffsetSecondary. see
    609      * details in {@link #updateSecondaryScrollLimits()}.
    610      */
    611     int mScrollOffsetSecondary;
    612 
    613     /**
    614      * User-specified row height/column width.  Can be WRAP_CONTENT.
    615      */
    616     private int mRowSizeSecondaryRequested;
    617 
    618     /**
    619      * The fixed size of each grid item in the secondary direction. This corresponds to
    620      * the row height, equal for all rows. Grid items may have variable length
    621      * in the primary direction.
    622      */
    623     private int mFixedRowSizeSecondary;
    624 
    625     /**
    626      * Tracks the secondary size of each row.
    627      */
    628     private int[] mRowSizeSecondary;
    629 
    630     /**
    631      * The maximum measured size of the view.
    632      */
    633     private int mMaxSizeSecondary;
    634 
    635     /**
    636      * Margin between items.
    637      */
    638     private int mHorizontalSpacing;
    639     /**
    640      * Margin between items vertically.
    641      */
    642     private int mVerticalSpacing;
    643     /**
    644      * Margin in main direction.
    645      */
    646     private int mSpacingPrimary;
    647     /**
    648      * Margin in second direction.
    649      */
    650     private int mSpacingSecondary;
    651     /**
    652      * How to position child in secondary direction.
    653      */
    654     private int mGravity = Gravity.START | Gravity.TOP;
    655     /**
    656      * The number of rows in the grid.
    657      */
    658     int mNumRows;
    659     /**
    660      * Number of rows requested, can be 0 to be determined by parent size and
    661      * rowHeight.
    662      */
    663     private int mNumRowsRequested = 1;
    664 
    665     /**
    666      * Saves grid information of each view.
    667      */
    668     Grid mGrid;
    669 
    670     /**
    671      * Focus Scroll strategy.
    672      */
    673     private int mFocusScrollStrategy = BaseGridView.FOCUS_SCROLL_ALIGNED;
    674     /**
    675      * Defines how item view is aligned in the window.
    676      */
    677     final WindowAlignment mWindowAlignment = new WindowAlignment();
    678 
    679     /**
    680      * Defines how item view is aligned.
    681      */
    682     private final ItemAlignment mItemAlignment = new ItemAlignment();
    683 
    684     /**
    685      * Dimensions of the view, width or height depending on orientation.
    686      */
    687     private int mSizePrimary;
    688 
    689     /**
    690      * Pixels of extra space for layout item (outside the widget)
    691      */
    692     private int mExtraLayoutSpace;
    693 
    694     /**
    695      * Temporary variable: an int array of length=2.
    696      */
    697     static int[] sTwoInts = new int[2];
    698 
    699     /**
    700      * Temporaries used for measuring.
    701      */
    702     private int[] mMeasuredDimension = new int[2];
    703 
    704     final ViewsStateBundle mChildrenStates = new ViewsStateBundle();
    705 
    706     /**
    707      * Optional interface implemented by Adapter.
    708      */
    709     private FacetProviderAdapter mFacetProviderAdapter;
    710 
    711     public GridLayoutManager(BaseGridView baseGridView) {
    712         mBaseGridView = baseGridView;
    713         mChildVisibility = -1;
    714         // disable prefetch by default, prefetch causes regression on low power chipset
    715         setItemPrefetchEnabled(false);
    716     }
    717 
    718     public void setOrientation(@RecyclerView.Orientation int orientation) {
    719         if (orientation != HORIZONTAL && orientation != VERTICAL) {
    720             if (DEBUG) Log.v(getTag(), "invalid orientation: " + orientation);
    721             return;
    722         }
    723 
    724         mOrientation = orientation;
    725         mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
    726         mWindowAlignment.setOrientation(orientation);
    727         mItemAlignment.setOrientation(orientation);
    728         mFlag |= PF_FORCE_FULL_LAYOUT;
    729     }
    730 
    731     public void onRtlPropertiesChanged(int layoutDirection) {
    732         final int flags;
    733         if (mOrientation == HORIZONTAL) {
    734             flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_PRIMARY : 0;
    735         } else {
    736             flags = layoutDirection == View.LAYOUT_DIRECTION_RTL ? PF_REVERSE_FLOW_SECONDARY : 0;
    737         }
    738         if ((mFlag & PF_REVERSE_FLOW_MASK) == flags) {
    739             return;
    740         }
    741         mFlag = (mFlag & ~PF_REVERSE_FLOW_MASK) | flags;
    742         mFlag |= PF_FORCE_FULL_LAYOUT;
    743         mWindowAlignment.horizontal.setReversedFlow(layoutDirection == View.LAYOUT_DIRECTION_RTL);
    744     }
    745 
    746     public int getFocusScrollStrategy() {
    747         return mFocusScrollStrategy;
    748     }
    749 
    750     public void setFocusScrollStrategy(int focusScrollStrategy) {
    751         mFocusScrollStrategy = focusScrollStrategy;
    752     }
    753 
    754     public void setWindowAlignment(int windowAlignment) {
    755         mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
    756     }
    757 
    758     public int getWindowAlignment() {
    759         return mWindowAlignment.mainAxis().getWindowAlignment();
    760     }
    761 
    762     public void setWindowAlignmentOffset(int alignmentOffset) {
    763         mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
    764     }
    765 
    766     public int getWindowAlignmentOffset() {
    767         return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
    768     }
    769 
    770     public void setWindowAlignmentOffsetPercent(float offsetPercent) {
    771         mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
    772     }
    773 
    774     public float getWindowAlignmentOffsetPercent() {
    775         return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
    776     }
    777 
    778     public void setItemAlignmentOffset(int alignmentOffset) {
    779         mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
    780         updateChildAlignments();
    781     }
    782 
    783     public int getItemAlignmentOffset() {
    784         return mItemAlignment.mainAxis().getItemAlignmentOffset();
    785     }
    786 
    787     public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
    788         mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
    789         updateChildAlignments();
    790     }
    791 
    792     public boolean isItemAlignmentOffsetWithPadding() {
    793         return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
    794     }
    795 
    796     public void setItemAlignmentOffsetPercent(float offsetPercent) {
    797         mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
    798         updateChildAlignments();
    799     }
    800 
    801     public float getItemAlignmentOffsetPercent() {
    802         return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
    803     }
    804 
    805     public void setItemAlignmentViewId(int viewId) {
    806         mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
    807         updateChildAlignments();
    808     }
    809 
    810     public int getItemAlignmentViewId() {
    811         return mItemAlignment.mainAxis().getItemAlignmentViewId();
    812     }
    813 
    814     public void setFocusOutAllowed(boolean throughFront, boolean throughEnd) {
    815         mFlag = (mFlag & ~PF_FOCUS_OUT_MASKS)
    816                 | (throughFront ? PF_FOCUS_OUT_FRONT : 0)
    817                 | (throughEnd ? PF_FOCUS_OUT_END : 0);
    818     }
    819 
    820     public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
    821         mFlag = (mFlag & ~PF_FOCUS_OUT_SIDE_MASKS)
    822                 | (throughStart ? PF_FOCUS_OUT_SIDE_START : 0)
    823                 | (throughEnd ? PF_FOCUS_OUT_SIDE_END : 0);
    824     }
    825 
    826     public void setNumRows(int numRows) {
    827         if (numRows < 0) throw new IllegalArgumentException();
    828         mNumRowsRequested = numRows;
    829     }
    830 
    831     /**
    832      * Set the row height. May be WRAP_CONTENT, or a size in pixels.
    833      */
    834     public void setRowHeight(int height) {
    835         if (height >= 0 || height == ViewGroup.LayoutParams.WRAP_CONTENT) {
    836             mRowSizeSecondaryRequested = height;
    837         } else {
    838             throw new IllegalArgumentException("Invalid row height: " + height);
    839         }
    840     }
    841 
    842     public void setItemSpacing(int space) {
    843         mVerticalSpacing = mHorizontalSpacing = space;
    844         mSpacingPrimary = mSpacingSecondary = space;
    845     }
    846 
    847     public void setVerticalSpacing(int space) {
    848         if (mOrientation == VERTICAL) {
    849             mSpacingPrimary = mVerticalSpacing = space;
    850         } else {
    851             mSpacingSecondary = mVerticalSpacing = space;
    852         }
    853     }
    854 
    855     public void setHorizontalSpacing(int space) {
    856         if (mOrientation == HORIZONTAL) {
    857             mSpacingPrimary = mHorizontalSpacing = space;
    858         } else {
    859             mSpacingSecondary = mHorizontalSpacing = space;
    860         }
    861     }
    862 
    863     public int getVerticalSpacing() {
    864         return mVerticalSpacing;
    865     }
    866 
    867     public int getHorizontalSpacing() {
    868         return mHorizontalSpacing;
    869     }
    870 
    871     public void setGravity(int gravity) {
    872         mGravity = gravity;
    873     }
    874 
    875     protected boolean hasDoneFirstLayout() {
    876         return mGrid != null;
    877     }
    878 
    879     public void setOnChildSelectedListener(OnChildSelectedListener listener) {
    880         mChildSelectedListener = listener;
    881     }
    882 
    883     public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
    884         if (listener == null) {
    885             mChildViewHolderSelectedListeners = null;
    886             return;
    887         }
    888         if (mChildViewHolderSelectedListeners == null) {
    889             mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
    890         } else {
    891             mChildViewHolderSelectedListeners.clear();
    892         }
    893         mChildViewHolderSelectedListeners.add(listener);
    894     }
    895 
    896     public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
    897         if (mChildViewHolderSelectedListeners == null) {
    898             mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
    899         }
    900         mChildViewHolderSelectedListeners.add(listener);
    901     }
    902 
    903     public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
    904             listener) {
    905         if (mChildViewHolderSelectedListeners != null) {
    906             mChildViewHolderSelectedListeners.remove(listener);
    907         }
    908     }
    909 
    910     boolean hasOnChildViewHolderSelectedListener() {
    911         return mChildViewHolderSelectedListeners != null
    912                 && mChildViewHolderSelectedListeners.size() > 0;
    913     }
    914 
    915     void fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
    916             int position, int subposition) {
    917         if (mChildViewHolderSelectedListeners == null) {
    918             return;
    919         }
    920         for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
    921             mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelected(parent, child,
    922                     position, subposition);
    923         }
    924     }
    925 
    926     void fireOnChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder
    927             child, int position, int subposition) {
    928         if (mChildViewHolderSelectedListeners == null) {
    929             return;
    930         }
    931         for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
    932             mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelectedAndPositioned(parent,
    933                     child, position, subposition);
    934         }
    935     }
    936 
    937     void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
    938         mChildLaidOutListener = listener;
    939     }
    940 
    941     private int getAdapterPositionByView(View view) {
    942         if (view == null) {
    943             return NO_POSITION;
    944         }
    945         LayoutParams params = (LayoutParams) view.getLayoutParams();
    946         if (params == null || params.isItemRemoved()) {
    947             // when item is removed, the position value can be any value.
    948             return NO_POSITION;
    949         }
    950         return params.getViewAdapterPosition();
    951     }
    952 
    953     int getSubPositionByView(View view, View childView) {
    954         if (view == null || childView == null) {
    955             return 0;
    956         }
    957         final LayoutParams lp = (LayoutParams) view.getLayoutParams();
    958         final ItemAlignmentFacet facet = lp.getItemAlignmentFacet();
    959         if (facet != null) {
    960             final ItemAlignmentFacet.ItemAlignmentDef[] defs = facet.getAlignmentDefs();
    961             if (defs.length > 1) {
    962                 while (childView != view) {
    963                     int id = childView.getId();
    964                     if (id != View.NO_ID) {
    965                         for (int i = 1; i < defs.length; i++) {
    966                             if (defs[i].getItemAlignmentFocusViewId() == id) {
    967                                 return i;
    968                             }
    969                         }
    970                     }
    971                     childView = (View) childView.getParent();
    972                 }
    973             }
    974         }
    975         return 0;
    976     }
    977 
    978     private int getAdapterPositionByIndex(int index) {
    979         return getAdapterPositionByView(getChildAt(index));
    980     }
    981 
    982     void dispatchChildSelected() {
    983         if (mChildSelectedListener == null && !hasOnChildViewHolderSelectedListener()) {
    984             return;
    985         }
    986 
    987         if (TRACE) TraceCompat.beginSection("onChildSelected");
    988         View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
    989         if (view != null) {
    990             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
    991             if (mChildSelectedListener != null) {
    992                 mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
    993                         vh == null? NO_ID: vh.getItemId());
    994             }
    995             fireOnChildViewHolderSelected(mBaseGridView, vh, mFocusPosition, mSubFocusPosition);
    996         } else {
    997             if (mChildSelectedListener != null) {
    998                 mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
    999             }
   1000             fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
   1001         }
   1002         if (TRACE) TraceCompat.endSection();
   1003 
   1004         // Children may request layout when a child selection event occurs (such as a change of
   1005         // padding on the current and previously selected rows).
   1006         // If in layout, a child requesting layout may have been laid out before the selection
   1007         // callback.
   1008         // If it was not, the child will be laid out after the selection callback.
   1009         // If so, the layout request will be honoured though the view system will emit a double-
   1010         // layout warning.
   1011         // If not in layout, we may be scrolling in which case the child layout request will be
   1012         // eaten by recyclerview.  Post a requestLayout.
   1013         if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && !mBaseGridView.isLayoutRequested()) {
   1014             int childCount = getChildCount();
   1015             for (int i = 0; i < childCount; i++) {
   1016                 if (getChildAt(i).isLayoutRequested()) {
   1017                     forceRequestLayout();
   1018                     break;
   1019                 }
   1020             }
   1021         }
   1022     }
   1023 
   1024     private void dispatchChildSelectedAndPositioned() {
   1025         if (!hasOnChildViewHolderSelectedListener()) {
   1026             return;
   1027         }
   1028 
   1029         if (TRACE) TraceCompat.beginSection("onChildSelectedAndPositioned");
   1030         View view = mFocusPosition == NO_POSITION ? null : findViewByPosition(mFocusPosition);
   1031         if (view != null) {
   1032             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(view);
   1033             fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, vh, mFocusPosition,
   1034                     mSubFocusPosition);
   1035         } else {
   1036             if (mChildSelectedListener != null) {
   1037                 mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
   1038             }
   1039             fireOnChildViewHolderSelectedAndPositioned(mBaseGridView, null, NO_POSITION, 0);
   1040         }
   1041         if (TRACE) TraceCompat.endSection();
   1042 
   1043     }
   1044 
   1045     @Override
   1046     public boolean canScrollHorizontally() {
   1047         // We can scroll horizontally if we have horizontal orientation, or if
   1048         // we are vertical and have more than one column.
   1049         return mOrientation == HORIZONTAL || mNumRows > 1;
   1050     }
   1051 
   1052     @Override
   1053     public boolean canScrollVertically() {
   1054         // We can scroll vertically if we have vertical orientation, or if we
   1055         // are horizontal and have more than one row.
   1056         return mOrientation == VERTICAL || mNumRows > 1;
   1057     }
   1058 
   1059     @Override
   1060     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
   1061         return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
   1062                 ViewGroup.LayoutParams.WRAP_CONTENT);
   1063     }
   1064 
   1065     @Override
   1066     public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
   1067         return new LayoutParams(context, attrs);
   1068     }
   1069 
   1070     @Override
   1071     public RecyclerView.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
   1072         if (lp instanceof LayoutParams) {
   1073             return new LayoutParams((LayoutParams) lp);
   1074         } else if (lp instanceof RecyclerView.LayoutParams) {
   1075             return new LayoutParams((RecyclerView.LayoutParams) lp);
   1076         } else if (lp instanceof MarginLayoutParams) {
   1077             return new LayoutParams((MarginLayoutParams) lp);
   1078         } else {
   1079             return new LayoutParams(lp);
   1080         }
   1081     }
   1082 
   1083     protected View getViewForPosition(int position) {
   1084         return mRecycler.getViewForPosition(position);
   1085     }
   1086 
   1087     final int getOpticalLeft(View v) {
   1088         return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
   1089     }
   1090 
   1091     final int getOpticalRight(View v) {
   1092         return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
   1093     }
   1094 
   1095     final int getOpticalTop(View v) {
   1096         return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
   1097     }
   1098 
   1099     final int getOpticalBottom(View v) {
   1100         return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
   1101     }
   1102 
   1103     @Override
   1104     public int getDecoratedLeft(View child) {
   1105         return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
   1106     }
   1107 
   1108     @Override
   1109     public int getDecoratedTop(View child) {
   1110         return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
   1111     }
   1112 
   1113     @Override
   1114     public int getDecoratedRight(View child) {
   1115         return super.getDecoratedRight(child)
   1116                 - ((LayoutParams) child.getLayoutParams()).mRightInset;
   1117     }
   1118 
   1119     @Override
   1120     public int getDecoratedBottom(View child) {
   1121         return super.getDecoratedBottom(child)
   1122                 - ((LayoutParams) child.getLayoutParams()).mBottomInset;
   1123     }
   1124 
   1125     @Override
   1126     public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
   1127         super.getDecoratedBoundsWithMargins(view, outBounds);
   1128         LayoutParams params = ((LayoutParams) view.getLayoutParams());
   1129         outBounds.left += params.mLeftInset;
   1130         outBounds.top += params.mTopInset;
   1131         outBounds.right -= params.mRightInset;
   1132         outBounds.bottom -= params.mBottomInset;
   1133     }
   1134 
   1135     int getViewMin(View v) {
   1136         return mOrientationHelper.getDecoratedStart(v);
   1137     }
   1138 
   1139     int getViewMax(View v) {
   1140         return mOrientationHelper.getDecoratedEnd(v);
   1141     }
   1142 
   1143     int getViewPrimarySize(View view) {
   1144         getDecoratedBoundsWithMargins(view, sTempRect);
   1145         return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
   1146     }
   1147 
   1148     private int getViewCenter(View view) {
   1149         return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
   1150     }
   1151 
   1152     private int getAdjustedViewCenter(View view) {
   1153         if (view.hasFocus()) {
   1154             View child = view.findFocus();
   1155             if (child != null && child != view) {
   1156                 return getAdjustedPrimaryAlignedScrollDistance(getViewCenter(view), view, child);
   1157             }
   1158         }
   1159         return getViewCenter(view);
   1160     }
   1161 
   1162     private int getViewCenterSecondary(View view) {
   1163         return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
   1164     }
   1165 
   1166     private int getViewCenterX(View v) {
   1167         LayoutParams p = (LayoutParams) v.getLayoutParams();
   1168         return p.getOpticalLeft(v) + p.getAlignX();
   1169     }
   1170 
   1171     private int getViewCenterY(View v) {
   1172         LayoutParams p = (LayoutParams) v.getLayoutParams();
   1173         return p.getOpticalTop(v) + p.getAlignY();
   1174     }
   1175 
   1176     /**
   1177      * Save Recycler and State for convenience.  Must be paired with leaveContext().
   1178      */
   1179     private void saveContext(Recycler recycler, State state) {
   1180         if (mRecycler != null || mState != null) {
   1181             Log.e(TAG, "Recycler information was not released, bug!");
   1182         }
   1183         mRecycler = recycler;
   1184         mState = state;
   1185         mPositionDeltaInPreLayout = 0;
   1186         mExtraLayoutSpaceInPreLayout = 0;
   1187     }
   1188 
   1189     /**
   1190      * Discard saved Recycler and State.
   1191      */
   1192     private void leaveContext() {
   1193         mRecycler = null;
   1194         mState = null;
   1195         mPositionDeltaInPreLayout = 0;
   1196         mExtraLayoutSpaceInPreLayout = 0;
   1197     }
   1198 
   1199     /**
   1200      * Re-initialize data structures for a data change or handling invisible
   1201      * selection. The method tries its best to preserve position information so
   1202      * that staggered grid looks same before and after re-initialize.
   1203      * @return true if can fastRelayout()
   1204      */
   1205     private boolean layoutInit() {
   1206         final int newItemCount = mState.getItemCount();
   1207         if (newItemCount == 0) {
   1208             mFocusPosition = NO_POSITION;
   1209             mSubFocusPosition = 0;
   1210         } else if (mFocusPosition >= newItemCount) {
   1211             mFocusPosition = newItemCount - 1;
   1212             mSubFocusPosition = 0;
   1213         } else if (mFocusPosition == NO_POSITION && newItemCount > 0) {
   1214             // if focus position is never set before,  initialize it to 0
   1215             mFocusPosition = 0;
   1216             mSubFocusPosition = 0;
   1217         }
   1218         if (!mState.didStructureChange() && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
   1219                 && (mFlag & PF_FORCE_FULL_LAYOUT) == 0 && mGrid.getNumRows() == mNumRows) {
   1220             updateScrollController();
   1221             updateSecondaryScrollLimits();
   1222             mGrid.setSpacing(mSpacingPrimary);
   1223             return true;
   1224         } else {
   1225             mFlag &= ~PF_FORCE_FULL_LAYOUT;
   1226 
   1227             if (mGrid == null || mNumRows != mGrid.getNumRows()
   1228                     || ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) != mGrid.isReversedFlow()) {
   1229                 mGrid = Grid.createGrid(mNumRows);
   1230                 mGrid.setProvider(mGridProvider);
   1231                 mGrid.setReversedFlow((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0);
   1232             }
   1233             initScrollController();
   1234             updateSecondaryScrollLimits();
   1235             mGrid.setSpacing(mSpacingPrimary);
   1236             detachAndScrapAttachedViews(mRecycler);
   1237             mGrid.resetVisibleIndex();
   1238             mWindowAlignment.mainAxis().invalidateScrollMin();
   1239             mWindowAlignment.mainAxis().invalidateScrollMax();
   1240             return false;
   1241         }
   1242     }
   1243 
   1244     private int getRowSizeSecondary(int rowIndex) {
   1245         if (mFixedRowSizeSecondary != 0) {
   1246             return mFixedRowSizeSecondary;
   1247         }
   1248         if (mRowSizeSecondary == null) {
   1249             return 0;
   1250         }
   1251         return mRowSizeSecondary[rowIndex];
   1252     }
   1253 
   1254     int getRowStartSecondary(int rowIndex) {
   1255         int start = 0;
   1256         // Iterate from left to right, which is a different index traversal
   1257         // in RTL flow
   1258         if ((mFlag & PF_REVERSE_FLOW_SECONDARY) != 0) {
   1259             for (int i = mNumRows-1; i > rowIndex; i--) {
   1260                 start += getRowSizeSecondary(i) + mSpacingSecondary;
   1261             }
   1262         } else {
   1263             for (int i = 0; i < rowIndex; i++) {
   1264                 start += getRowSizeSecondary(i) + mSpacingSecondary;
   1265             }
   1266         }
   1267         return start;
   1268     }
   1269 
   1270     private int getSizeSecondary() {
   1271         int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
   1272         return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
   1273     }
   1274 
   1275     int getDecoratedMeasuredWidthWithMargin(View v) {
   1276         final LayoutParams lp = (LayoutParams) v.getLayoutParams();
   1277         return getDecoratedMeasuredWidth(v) + lp.leftMargin + lp.rightMargin;
   1278     }
   1279 
   1280     int getDecoratedMeasuredHeightWithMargin(View v) {
   1281         final LayoutParams lp = (LayoutParams) v.getLayoutParams();
   1282         return getDecoratedMeasuredHeight(v) + lp.topMargin + lp.bottomMargin;
   1283     }
   1284 
   1285     private void measureScrapChild(int position, int widthSpec, int heightSpec,
   1286             int[] measuredDimension) {
   1287         View view = mRecycler.getViewForPosition(position);
   1288         if (view != null) {
   1289             final LayoutParams p = (LayoutParams) view.getLayoutParams();
   1290             calculateItemDecorationsForChild(view, sTempRect);
   1291             int widthUsed = p.leftMargin + p.rightMargin + sTempRect.left + sTempRect.right;
   1292             int heightUsed = p.topMargin + p.bottomMargin + sTempRect.top + sTempRect.bottom;
   1293 
   1294             int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
   1295                     getPaddingLeft() + getPaddingRight() + widthUsed, p.width);
   1296             int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
   1297                     getPaddingTop() + getPaddingBottom() + heightUsed, p.height);
   1298             view.measure(childWidthSpec, childHeightSpec);
   1299 
   1300             measuredDimension[0] = getDecoratedMeasuredWidthWithMargin(view);
   1301             measuredDimension[1] = getDecoratedMeasuredHeightWithMargin(view);
   1302             mRecycler.recycleView(view);
   1303         }
   1304     }
   1305 
   1306     private boolean processRowSizeSecondary(boolean measure) {
   1307         if (mFixedRowSizeSecondary != 0 || mRowSizeSecondary == null) {
   1308             return false;
   1309         }
   1310 
   1311         if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
   1312         CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
   1313         boolean changed = false;
   1314         int scrapeChildSize = -1;
   1315 
   1316         for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
   1317             CircularIntArray row = rows == null ? null : rows[rowIndex];
   1318             final int rowItemsPairCount = row == null ? 0 : row.size();
   1319             int rowSize = -1;
   1320             for (int rowItemPairIndex = 0; rowItemPairIndex < rowItemsPairCount;
   1321                     rowItemPairIndex += 2) {
   1322                 final int rowIndexStart = row.get(rowItemPairIndex);
   1323                 final int rowIndexEnd = row.get(rowItemPairIndex + 1);
   1324                 for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
   1325                     final View view = findViewByPosition(i - mPositionDeltaInPreLayout);
   1326                     if (view == null) {
   1327                         continue;
   1328                     }
   1329                     if (measure) {
   1330                         measureChild(view);
   1331                     }
   1332                     final int secondarySize = mOrientation == HORIZONTAL
   1333                             ? getDecoratedMeasuredHeightWithMargin(view)
   1334                             : getDecoratedMeasuredWidthWithMargin(view);
   1335                     if (secondarySize > rowSize) {
   1336                         rowSize = secondarySize;
   1337                     }
   1338                 }
   1339             }
   1340 
   1341             final int itemCount = mState.getItemCount();
   1342             if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
   1343                 if (scrapeChildSize < 0) {
   1344                     // measure a child that is close to mFocusPosition but not currently visible
   1345                     int position = mFocusPosition;
   1346                     if (position < 0) {
   1347                         position = 0;
   1348                     } else if (position >= itemCount) {
   1349                         position = itemCount - 1;
   1350                     }
   1351                     if (getChildCount() > 0) {
   1352                         int firstPos = mBaseGridView.getChildViewHolder(
   1353                                 getChildAt(0)).getLayoutPosition();
   1354                         int lastPos = mBaseGridView.getChildViewHolder(
   1355                                 getChildAt(getChildCount() - 1)).getLayoutPosition();
   1356                         // if mFocusPosition is between first and last, choose either
   1357                         // first - 1 or last + 1
   1358                         if (position >= firstPos && position <= lastPos) {
   1359                             position = (position - firstPos <= lastPos - position)
   1360                                     ? (firstPos - 1) : (lastPos + 1);
   1361                             // try the other value if the position is invalid. if both values are
   1362                             // invalid, skip measureScrapChild below.
   1363                             if (position < 0 && lastPos < itemCount - 1) {
   1364                                 position = lastPos + 1;
   1365                             } else if (position >= itemCount && firstPos > 0) {
   1366                                 position = firstPos - 1;
   1367                             }
   1368                         }
   1369                     }
   1370                     if (position >= 0 && position < itemCount) {
   1371                         measureScrapChild(position,
   1372                                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
   1373                                 MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
   1374                                 mMeasuredDimension);
   1375                         scrapeChildSize = mOrientation == HORIZONTAL ? mMeasuredDimension[1] :
   1376                                 mMeasuredDimension[0];
   1377                         if (DEBUG) {
   1378                             Log.v(TAG, "measured scrap child: " + mMeasuredDimension[0] + " "
   1379                                     + mMeasuredDimension[1]);
   1380                         }
   1381                     }
   1382                 }
   1383                 if (scrapeChildSize >= 0) {
   1384                     rowSize = scrapeChildSize;
   1385                 }
   1386             }
   1387             if (rowSize < 0) {
   1388                 rowSize = 0;
   1389             }
   1390             if (mRowSizeSecondary[rowIndex] != rowSize) {
   1391                 if (DEBUG) {
   1392                     Log.v(getTag(), "row size secondary changed: " + mRowSizeSecondary[rowIndex]
   1393                             + ", " + rowSize);
   1394                 }
   1395                 mRowSizeSecondary[rowIndex] = rowSize;
   1396                 changed = true;
   1397             }
   1398         }
   1399 
   1400         if (TRACE) TraceCompat.endSection();
   1401         return changed;
   1402     }
   1403 
   1404     /**
   1405      * Checks if we need to update row secondary sizes.
   1406      */
   1407     private void updateRowSecondarySizeRefresh() {
   1408         mFlag = (mFlag & ~PF_ROW_SECONDARY_SIZE_REFRESH)
   1409                 | (processRowSizeSecondary(false) ? PF_ROW_SECONDARY_SIZE_REFRESH : 0);
   1410         if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
   1411             if (DEBUG) Log.v(getTag(), "mRowSecondarySizeRefresh now set");
   1412             forceRequestLayout();
   1413         }
   1414     }
   1415 
   1416     private void forceRequestLayout() {
   1417         if (DEBUG) Log.v(getTag(), "forceRequestLayout");
   1418         // RecyclerView prevents us from requesting layout in many cases
   1419         // (during layout, during scroll, etc.)
   1420         // For secondary row size wrap_content support we currently need a
   1421         // second layout pass to update the measured size after having measured
   1422         // and added child views in layoutChildren.
   1423         // Force the second layout by posting a delayed runnable.
   1424         // TODO: investigate allowing a second layout pass,
   1425         // or move child add/measure logic to the measure phase.
   1426         ViewCompat.postOnAnimation(mBaseGridView, mRequestLayoutRunnable);
   1427     }
   1428 
   1429     private final Runnable mRequestLayoutRunnable = new Runnable() {
   1430         @Override
   1431         public void run() {
   1432             if (DEBUG) Log.v(getTag(), "request Layout from runnable");
   1433             requestLayout();
   1434         }
   1435     };
   1436 
   1437     @Override
   1438     public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
   1439         saveContext(recycler, state);
   1440 
   1441         int sizePrimary, sizeSecondary, modeSecondary, paddingSecondary;
   1442         int measuredSizeSecondary;
   1443         if (mOrientation == HORIZONTAL) {
   1444             sizePrimary = MeasureSpec.getSize(widthSpec);
   1445             sizeSecondary = MeasureSpec.getSize(heightSpec);
   1446             modeSecondary = MeasureSpec.getMode(heightSpec);
   1447             paddingSecondary = getPaddingTop() + getPaddingBottom();
   1448         } else {
   1449             sizeSecondary = MeasureSpec.getSize(widthSpec);
   1450             sizePrimary = MeasureSpec.getSize(heightSpec);
   1451             modeSecondary = MeasureSpec.getMode(widthSpec);
   1452             paddingSecondary = getPaddingLeft() + getPaddingRight();
   1453         }
   1454         if (DEBUG) {
   1455             Log.v(getTag(), "onMeasure widthSpec " + Integer.toHexString(widthSpec)
   1456                     + " heightSpec " + Integer.toHexString(heightSpec)
   1457                     + " modeSecondary " + Integer.toHexString(modeSecondary)
   1458                     + " sizeSecondary " + sizeSecondary + " " + this);
   1459         }
   1460 
   1461         mMaxSizeSecondary = sizeSecondary;
   1462 
   1463         if (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT) {
   1464             mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
   1465             mFixedRowSizeSecondary = 0;
   1466 
   1467             if (mRowSizeSecondary == null || mRowSizeSecondary.length != mNumRows) {
   1468                 mRowSizeSecondary = new int[mNumRows];
   1469             }
   1470 
   1471             if (mState.isPreLayout()) {
   1472                 updatePositionDeltaInPreLayout();
   1473             }
   1474             // Measure all current children and update cached row height or column width
   1475             processRowSizeSecondary(true);
   1476 
   1477             switch (modeSecondary) {
   1478                 case MeasureSpec.UNSPECIFIED:
   1479                     measuredSizeSecondary = getSizeSecondary() + paddingSecondary;
   1480                     break;
   1481                 case MeasureSpec.AT_MOST:
   1482                     measuredSizeSecondary = Math.min(getSizeSecondary() + paddingSecondary,
   1483                             mMaxSizeSecondary);
   1484                     break;
   1485                 case MeasureSpec.EXACTLY:
   1486                     measuredSizeSecondary = mMaxSizeSecondary;
   1487                     break;
   1488                 default:
   1489                     throw new IllegalStateException("wrong spec");
   1490             }
   1491 
   1492         } else {
   1493             switch (modeSecondary) {
   1494                 case MeasureSpec.UNSPECIFIED:
   1495                     mFixedRowSizeSecondary = mRowSizeSecondaryRequested == 0
   1496                             ? sizeSecondary - paddingSecondary : mRowSizeSecondaryRequested;
   1497                     mNumRows = mNumRowsRequested == 0 ? 1 : mNumRowsRequested;
   1498                     measuredSizeSecondary = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
   1499                             * (mNumRows - 1) + paddingSecondary;
   1500                     break;
   1501                 case MeasureSpec.AT_MOST:
   1502                 case MeasureSpec.EXACTLY:
   1503                     if (mNumRowsRequested == 0 && mRowSizeSecondaryRequested == 0) {
   1504                         mNumRows = 1;
   1505                         mFixedRowSizeSecondary = sizeSecondary - paddingSecondary;
   1506                     } else if (mNumRowsRequested == 0) {
   1507                         mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
   1508                         mNumRows = (sizeSecondary + mSpacingSecondary)
   1509                                 / (mRowSizeSecondaryRequested + mSpacingSecondary);
   1510                     } else if (mRowSizeSecondaryRequested == 0) {
   1511                         mNumRows = mNumRowsRequested;
   1512                         mFixedRowSizeSecondary = (sizeSecondary - paddingSecondary
   1513                                 - mSpacingSecondary * (mNumRows - 1)) / mNumRows;
   1514                     } else {
   1515                         mNumRows = mNumRowsRequested;
   1516                         mFixedRowSizeSecondary = mRowSizeSecondaryRequested;
   1517                     }
   1518                     measuredSizeSecondary = sizeSecondary;
   1519                     if (modeSecondary == MeasureSpec.AT_MOST) {
   1520                         int childrenSize = mFixedRowSizeSecondary * mNumRows + mSpacingSecondary
   1521                                 * (mNumRows - 1) + paddingSecondary;
   1522                         if (childrenSize < measuredSizeSecondary) {
   1523                             measuredSizeSecondary = childrenSize;
   1524                         }
   1525                     }
   1526                     break;
   1527                 default:
   1528                     throw new IllegalStateException("wrong spec");
   1529             }
   1530         }
   1531         if (mOrientation == HORIZONTAL) {
   1532             setMeasuredDimension(sizePrimary, measuredSizeSecondary);
   1533         } else {
   1534             setMeasuredDimension(measuredSizeSecondary, sizePrimary);
   1535         }
   1536         if (DEBUG) {
   1537             Log.v(getTag(), "onMeasure sizePrimary " + sizePrimary
   1538                     + " measuredSizeSecondary " + measuredSizeSecondary
   1539                     + " mFixedRowSizeSecondary " + mFixedRowSizeSecondary
   1540                     + " mNumRows " + mNumRows);
   1541         }
   1542         leaveContext();
   1543     }
   1544 
   1545     void measureChild(View child) {
   1546         if (TRACE) TraceCompat.beginSection("measureChild");
   1547         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
   1548         calculateItemDecorationsForChild(child, sTempRect);
   1549         int widthUsed = lp.leftMargin + lp.rightMargin + sTempRect.left + sTempRect.right;
   1550         int heightUsed = lp.topMargin + lp.bottomMargin + sTempRect.top + sTempRect.bottom;
   1551 
   1552         final int secondarySpec =
   1553                 (mRowSizeSecondaryRequested == ViewGroup.LayoutParams.WRAP_CONTENT)
   1554                         ? MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
   1555                         : MeasureSpec.makeMeasureSpec(mFixedRowSizeSecondary, MeasureSpec.EXACTLY);
   1556         int widthSpec, heightSpec;
   1557 
   1558         if (mOrientation == HORIZONTAL) {
   1559             widthSpec = ViewGroup.getChildMeasureSpec(
   1560                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), widthUsed, lp.width);
   1561             heightSpec = ViewGroup.getChildMeasureSpec(secondarySpec, heightUsed, lp.height);
   1562         } else {
   1563             heightSpec = ViewGroup.getChildMeasureSpec(
   1564                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed, lp.height);
   1565             widthSpec = ViewGroup.getChildMeasureSpec(secondarySpec, widthUsed, lp.width);
   1566         }
   1567         child.measure(widthSpec, heightSpec);
   1568         if (DEBUG) {
   1569             Log.v(getTag(), "measureChild secondarySpec " + Integer.toHexString(secondarySpec)
   1570                     + " widthSpec " + Integer.toHexString(widthSpec)
   1571                     + " heightSpec " + Integer.toHexString(heightSpec)
   1572                     + " measuredWidth " + child.getMeasuredWidth()
   1573                     + " measuredHeight " + child.getMeasuredHeight());
   1574         }
   1575         if (DEBUG) Log.v(getTag(), "child lp width " + lp.width + " height " + lp.height);
   1576         if (TRACE) TraceCompat.endSection();
   1577     }
   1578 
   1579     /**
   1580      * Get facet from the ViewHolder or the viewType.
   1581      */
   1582     <E> E getFacet(RecyclerView.ViewHolder vh, Class<? extends E> facetClass) {
   1583         E facet = null;
   1584         if (vh instanceof FacetProvider) {
   1585             facet = (E) ((FacetProvider) vh).getFacet(facetClass);
   1586         }
   1587         if (facet == null && mFacetProviderAdapter != null) {
   1588             FacetProvider p = mFacetProviderAdapter.getFacetProvider(vh.getItemViewType());
   1589             if (p != null) {
   1590                 facet = (E) p.getFacet(facetClass);
   1591             }
   1592         }
   1593         return facet;
   1594     }
   1595 
   1596     private Grid.Provider mGridProvider = new Grid.Provider() {
   1597 
   1598         @Override
   1599         public int getMinIndex() {
   1600             return mPositionDeltaInPreLayout;
   1601         }
   1602 
   1603         @Override
   1604         public int getCount() {
   1605             return mState.getItemCount() + mPositionDeltaInPreLayout;
   1606         }
   1607 
   1608         @Override
   1609         public int createItem(int index, boolean append, Object[] item, boolean disappearingItem) {
   1610             if (TRACE) TraceCompat.beginSection("createItem");
   1611             if (TRACE) TraceCompat.beginSection("getview");
   1612             View v = getViewForPosition(index - mPositionDeltaInPreLayout);
   1613             if (TRACE) TraceCompat.endSection();
   1614             LayoutParams lp = (LayoutParams) v.getLayoutParams();
   1615             RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
   1616             lp.setItemAlignmentFacet((ItemAlignmentFacet)getFacet(vh, ItemAlignmentFacet.class));
   1617             // See recyclerView docs:  we don't need re-add scraped view if it was removed.
   1618             if (!lp.isItemRemoved()) {
   1619                 if (TRACE) TraceCompat.beginSection("addView");
   1620                 if (disappearingItem) {
   1621                     if (append) {
   1622                         addDisappearingView(v);
   1623                     } else {
   1624                         addDisappearingView(v, 0);
   1625                     }
   1626                 } else {
   1627                     if (append) {
   1628                         addView(v);
   1629                     } else {
   1630                         addView(v, 0);
   1631                     }
   1632                 }
   1633                 if (TRACE) TraceCompat.endSection();
   1634                 if (mChildVisibility != -1) {
   1635                     v.setVisibility(mChildVisibility);
   1636                 }
   1637 
   1638                 if (mPendingMoveSmoothScroller != null) {
   1639                     mPendingMoveSmoothScroller.consumePendingMovesBeforeLayout();
   1640                 }
   1641                 int subindex = getSubPositionByView(v, v.findFocus());
   1642                 if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
   1643                     // when we are appending item during scroll pass and the item's position
   1644                     // matches the mFocusPosition,  we should signal a childSelected event.
   1645                     // However if we are still running PendingMoveSmoothScroller,  we defer and
   1646                     // signal the event in PendingMoveSmoothScroller.onStop().  This can
   1647                     // avoid lots of childSelected events during a long smooth scrolling and
   1648                     // increase performance.
   1649                     if (index == mFocusPosition && subindex == mSubFocusPosition
   1650                             && mPendingMoveSmoothScroller == null) {
   1651                         dispatchChildSelected();
   1652                     }
   1653                 } else if ((mFlag & PF_FAST_RELAYOUT) == 0) {
   1654                     // fastRelayout will dispatch event at end of onLayoutChildren().
   1655                     // For full layout, two situations here:
   1656                     // 1. mInLayoutSearchFocus is false, dispatchChildSelected() at mFocusPosition.
   1657                     // 2. mInLayoutSearchFocus is true:  dispatchChildSelected() on first child
   1658                     //    equal to or after mFocusPosition that can take focus.
   1659                     if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) == 0 && index == mFocusPosition
   1660                             && subindex == mSubFocusPosition) {
   1661                         dispatchChildSelected();
   1662                     } else if ((mFlag & PF_IN_LAYOUT_SEARCH_FOCUS) != 0 && index >= mFocusPosition
   1663                             && v.hasFocusable()) {
   1664                         mFocusPosition = index;
   1665                         mSubFocusPosition = subindex;
   1666                         mFlag &= ~PF_IN_LAYOUT_SEARCH_FOCUS;
   1667                         dispatchChildSelected();
   1668                     }
   1669                 }
   1670                 measureChild(v);
   1671             }
   1672             item[0] = v;
   1673             return mOrientation == HORIZONTAL ? getDecoratedMeasuredWidthWithMargin(v)
   1674                     : getDecoratedMeasuredHeightWithMargin(v);
   1675         }
   1676 
   1677         @Override
   1678         public void addItem(Object item, int index, int length, int rowIndex, int edge) {
   1679             View v = (View) item;
   1680             int start, end;
   1681             if (edge == Integer.MIN_VALUE || edge == Integer.MAX_VALUE) {
   1682                 edge = !mGrid.isReversedFlow() ? mWindowAlignment.mainAxis().getPaddingMin()
   1683                         : mWindowAlignment.mainAxis().getSize()
   1684                                 - mWindowAlignment.mainAxis().getPaddingMax();
   1685             }
   1686             boolean edgeIsMin = !mGrid.isReversedFlow();
   1687             if (edgeIsMin) {
   1688                 start = edge;
   1689                 end = edge + length;
   1690             } else {
   1691                 start = edge - length;
   1692                 end = edge;
   1693             }
   1694             int startSecondary = getRowStartSecondary(rowIndex)
   1695                     + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
   1696             mChildrenStates.loadView(v, index);
   1697             layoutChild(rowIndex, v, start, end, startSecondary);
   1698             if (DEBUG) {
   1699                 Log.d(getTag(), "addView " + index + " " + v);
   1700             }
   1701             if (TRACE) TraceCompat.endSection();
   1702 
   1703             if (!mState.isPreLayout()) {
   1704                 updateScrollLimits();
   1705             }
   1706             if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT && mPendingMoveSmoothScroller != null) {
   1707                 mPendingMoveSmoothScroller.consumePendingMovesAfterLayout();
   1708             }
   1709             if (mChildLaidOutListener != null) {
   1710                 RecyclerView.ViewHolder vh = mBaseGridView.getChildViewHolder(v);
   1711                 mChildLaidOutListener.onChildLaidOut(mBaseGridView, v, index,
   1712                         vh == null ? NO_ID : vh.getItemId());
   1713             }
   1714         }
   1715 
   1716         @Override
   1717         public void removeItem(int index) {
   1718             if (TRACE) TraceCompat.beginSection("removeItem");
   1719             View v = findViewByPosition(index - mPositionDeltaInPreLayout);
   1720             if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
   1721                 detachAndScrapView(v, mRecycler);
   1722             } else {
   1723                 removeAndRecycleView(v, mRecycler);
   1724             }
   1725             if (TRACE) TraceCompat.endSection();
   1726         }
   1727 
   1728         @Override
   1729         public int getEdge(int index) {
   1730             View v = findViewByPosition(index - mPositionDeltaInPreLayout);
   1731             return (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? getViewMax(v) : getViewMin(v);
   1732         }
   1733 
   1734         @Override
   1735         public int getSize(int index) {
   1736             return getViewPrimarySize(findViewByPosition(index - mPositionDeltaInPreLayout));
   1737         }
   1738     };
   1739 
   1740     void layoutChild(int rowIndex, View v, int start, int end, int startSecondary) {
   1741         if (TRACE) TraceCompat.beginSection("layoutChild");
   1742         int sizeSecondary = mOrientation == HORIZONTAL ? getDecoratedMeasuredHeightWithMargin(v)
   1743                 : getDecoratedMeasuredWidthWithMargin(v);
   1744         if (mFixedRowSizeSecondary > 0) {
   1745             sizeSecondary = Math.min(sizeSecondary, mFixedRowSizeSecondary);
   1746         }
   1747         final int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
   1748         final int horizontalGravity = (mFlag & PF_REVERSE_FLOW_MASK) != 0
   1749                 ? Gravity.getAbsoluteGravity(mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK,
   1750                 View.LAYOUT_DIRECTION_RTL)
   1751                 : mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
   1752         if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.TOP)
   1753                 || (mOrientation == VERTICAL && horizontalGravity == Gravity.LEFT)) {
   1754             // do nothing
   1755         } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.BOTTOM)
   1756                 || (mOrientation == VERTICAL && horizontalGravity == Gravity.RIGHT)) {
   1757             startSecondary += getRowSizeSecondary(rowIndex) - sizeSecondary;
   1758         } else if ((mOrientation == HORIZONTAL && verticalGravity == Gravity.CENTER_VERTICAL)
   1759                 || (mOrientation == VERTICAL && horizontalGravity == Gravity.CENTER_HORIZONTAL)) {
   1760             startSecondary += (getRowSizeSecondary(rowIndex) - sizeSecondary) / 2;
   1761         }
   1762         int left, top, right, bottom;
   1763         if (mOrientation == HORIZONTAL) {
   1764             left = start;
   1765             top = startSecondary;
   1766             right = end;
   1767             bottom = startSecondary + sizeSecondary;
   1768         } else {
   1769             top = start;
   1770             left = startSecondary;
   1771             bottom = end;
   1772             right = startSecondary + sizeSecondary;
   1773         }
   1774         LayoutParams params = (LayoutParams) v.getLayoutParams();
   1775         layoutDecoratedWithMargins(v, left, top, right, bottom);
   1776         // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
   1777         // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
   1778         // bounds insets.
   1779         super.getDecoratedBoundsWithMargins(v, sTempRect);
   1780         params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
   1781                 sTempRect.right - right, sTempRect.bottom - bottom);
   1782         updateChildAlignments(v);
   1783         if (TRACE) TraceCompat.endSection();
   1784     }
   1785 
   1786     private void updateChildAlignments(View v) {
   1787         final LayoutParams p = (LayoutParams) v.getLayoutParams();
   1788         if (p.getItemAlignmentFacet() == null) {
   1789             // Fallback to global settings on grid view
   1790             p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
   1791             p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
   1792         } else {
   1793             // Use ItemAlignmentFacet defined on specific ViewHolder
   1794             p.calculateItemAlignments(mOrientation, v);
   1795             if (mOrientation == HORIZONTAL) {
   1796                 p.setAlignY(mItemAlignment.vertical.getAlignmentPosition(v));
   1797             } else {
   1798                 p.setAlignX(mItemAlignment.horizontal.getAlignmentPosition(v));
   1799             }
   1800         }
   1801     }
   1802 
   1803     private void updateChildAlignments() {
   1804         for (int i = 0, c = getChildCount(); i < c; i++) {
   1805             updateChildAlignments(getChildAt(i));
   1806         }
   1807     }
   1808 
   1809     void setExtraLayoutSpace(int extraLayoutSpace) {
   1810         if (mExtraLayoutSpace == extraLayoutSpace) {
   1811             return;
   1812         } else if (mExtraLayoutSpace < 0) {
   1813             throw new IllegalArgumentException("ExtraLayoutSpace must >= 0");
   1814         }
   1815         mExtraLayoutSpace = extraLayoutSpace;
   1816         requestLayout();
   1817     }
   1818 
   1819     int getExtraLayoutSpace() {
   1820         return mExtraLayoutSpace;
   1821     }
   1822 
   1823     private void removeInvisibleViewsAtEnd() {
   1824         if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
   1825             mGrid.removeInvisibleItemsAtEnd(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
   1826                     ? -mExtraLayoutSpace : mSizePrimary + mExtraLayoutSpace);
   1827         }
   1828     }
   1829 
   1830     private void removeInvisibleViewsAtFront() {
   1831         if ((mFlag & (PF_PRUNE_CHILD | PF_SLIDING)) == PF_PRUNE_CHILD) {
   1832             mGrid.removeInvisibleItemsAtFront(mFocusPosition, (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
   1833                     ? mSizePrimary + mExtraLayoutSpace : -mExtraLayoutSpace);
   1834         }
   1835     }
   1836 
   1837     private boolean appendOneColumnVisibleItems() {
   1838         return mGrid.appendOneColumnVisibleItems();
   1839     }
   1840 
   1841     void slideIn() {
   1842         if ((mFlag & PF_SLIDING) != 0) {
   1843             mFlag &= ~PF_SLIDING;
   1844             if (mFocusPosition >= 0) {
   1845                 scrollToSelection(mFocusPosition, mSubFocusPosition, true, mPrimaryScrollExtra);
   1846             } else {
   1847                 mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
   1848                 requestLayout();
   1849             }
   1850             if ((mFlag & PF_LAYOUT_EATEN_IN_SLIDING) != 0) {
   1851                 mFlag &= ~PF_LAYOUT_EATEN_IN_SLIDING;
   1852                 if (mBaseGridView.getScrollState() != SCROLL_STATE_IDLE || isSmoothScrolling()) {
   1853                     mBaseGridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
   1854                         @Override
   1855                         public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
   1856                             if (newState == SCROLL_STATE_IDLE) {
   1857                                 mBaseGridView.removeOnScrollListener(this);
   1858                                 requestLayout();
   1859                             }
   1860                         }
   1861                     });
   1862                 } else {
   1863                     requestLayout();
   1864                 }
   1865             }
   1866         }
   1867     }
   1868 
   1869     int getSlideOutDistance() {
   1870         int distance;
   1871         if (mOrientation == VERTICAL) {
   1872             distance = -getHeight();
   1873             if (getChildCount() > 0) {
   1874                 int top = getChildAt(0).getTop();
   1875                 if (top < 0) {
   1876                     // scroll more if first child is above top edge
   1877                     distance = distance + top;
   1878                 }
   1879             }
   1880         } else {
   1881             if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0) {
   1882                 distance = getWidth();
   1883                 if (getChildCount() > 0) {
   1884                     int start = getChildAt(0).getRight();
   1885                     if (start > distance) {
   1886                         // scroll more if first child is outside right edge
   1887                         distance = start;
   1888                     }
   1889                 }
   1890             } else {
   1891                 distance = -getWidth();
   1892                 if (getChildCount() > 0) {
   1893                     int start = getChildAt(0).getLeft();
   1894                     if (start < 0) {
   1895                         // scroll more if first child is out side left edge
   1896                         distance = distance + start;
   1897                     }
   1898                 }
   1899             }
   1900         }
   1901         return distance;
   1902     }
   1903 
   1904     boolean isSlidingChildViews() {
   1905         return (mFlag & PF_SLIDING) != 0;
   1906     }
   1907 
   1908     /**
   1909      * Temporarily slide out child and block layout and scroll requests.
   1910      */
   1911     void slideOut() {
   1912         if ((mFlag & PF_SLIDING) != 0) {
   1913             return;
   1914         }
   1915         mFlag |= PF_SLIDING;
   1916         if (getChildCount() == 0) {
   1917             return;
   1918         }
   1919         if (mOrientation == VERTICAL) {
   1920             mBaseGridView.smoothScrollBy(0, getSlideOutDistance(),
   1921                     new AccelerateDecelerateInterpolator());
   1922         } else {
   1923             mBaseGridView.smoothScrollBy(getSlideOutDistance(), 0,
   1924                     new AccelerateDecelerateInterpolator());
   1925         }
   1926     }
   1927 
   1928     private boolean prependOneColumnVisibleItems() {
   1929         return mGrid.prependOneColumnVisibleItems();
   1930     }
   1931 
   1932     private void appendVisibleItems() {
   1933         mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
   1934                 ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
   1935                 : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
   1936     }
   1937 
   1938     private void prependVisibleItems() {
   1939         mGrid.prependVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
   1940                 ? mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout
   1941                 : -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout);
   1942     }
   1943 
   1944     /**
   1945      * Fast layout when there is no structure change, adapter change, etc.
   1946      * It will layout all views was layout requested or updated, until hit a view
   1947      * with different size,  then it break and detachAndScrap all views after that.
   1948      */
   1949     private void fastRelayout() {
   1950         boolean invalidateAfter = false;
   1951         final int childCount = getChildCount();
   1952         int position = mGrid.getFirstVisibleIndex();
   1953         int index = 0;
   1954         mFlag &= ~PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
   1955         for (; index < childCount; index++, position++) {
   1956             View view = getChildAt(index);
   1957             // We don't hit fastRelayout() if State.didStructure() is true, but prelayout may add
   1958             // extra views and invalidate existing Grid position. Also the prelayout calling
   1959             // getViewForPosotion() may retrieve item from cache with FLAG_INVALID. The adapter
   1960             // postion will be -1 for this case. Either case, we should invalidate after this item
   1961             // and call getViewForPosition() again to rebind.
   1962             if (position != getAdapterPositionByView(view)) {
   1963                 invalidateAfter = true;
   1964                 break;
   1965             }
   1966             Grid.Location location = mGrid.getLocation(position);
   1967             if (location == null) {
   1968                 invalidateAfter = true;
   1969                 break;
   1970             }
   1971 
   1972             int startSecondary = getRowStartSecondary(location.row)
   1973                     + mWindowAlignment.secondAxis().getPaddingMin() - mScrollOffsetSecondary;
   1974             int primarySize, end;
   1975             int start = getViewMin(view);
   1976             int oldPrimarySize = getViewPrimarySize(view);
   1977 
   1978             LayoutParams lp = (LayoutParams) view.getLayoutParams();
   1979             if (lp.viewNeedsUpdate()) {
   1980                 mFlag |= PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION;
   1981                 detachAndScrapView(view, mRecycler);
   1982                 view = getViewForPosition(position);
   1983                 addView(view, index);
   1984             }
   1985 
   1986             measureChild(view);
   1987             if (mOrientation == HORIZONTAL) {
   1988                 primarySize = getDecoratedMeasuredWidthWithMargin(view);
   1989                 end = start + primarySize;
   1990             } else {
   1991                 primarySize = getDecoratedMeasuredHeightWithMargin(view);
   1992                 end = start + primarySize;
   1993             }
   1994             layoutChild(location.row, view, start, end, startSecondary);
   1995             if (oldPrimarySize != primarySize) {
   1996                 // size changed invalidate remaining Locations
   1997                 if (DEBUG) Log.d(getTag(), "fastRelayout: view size changed at " + position);
   1998                 invalidateAfter = true;
   1999                 break;
   2000             }
   2001         }
   2002         if (invalidateAfter) {
   2003             final int savedLastPos = mGrid.getLastVisibleIndex();
   2004             for (int i = childCount - 1; i >= index; i--) {
   2005                 View v = getChildAt(i);
   2006                 detachAndScrapView(v, mRecycler);
   2007             }
   2008             mGrid.invalidateItemsAfter(position);
   2009             if ((mFlag & PF_PRUNE_CHILD) != 0) {
   2010                 // in regular prune child mode, we just append items up to edge limit
   2011                 appendVisibleItems();
   2012                 if (mFocusPosition >= 0 && mFocusPosition <= savedLastPos) {
   2013                     // make sure add focus view back:  the view might be outside edge limit
   2014                     // when there is delta in onLayoutChildren().
   2015                     while (mGrid.getLastVisibleIndex() < mFocusPosition) {
   2016                         mGrid.appendOneColumnVisibleItems();
   2017                     }
   2018                 }
   2019             } else {
   2020                 // prune disabled(e.g. in RowsFragment transition): append all removed items
   2021                 while (mGrid.appendOneColumnVisibleItems()
   2022                         && mGrid.getLastVisibleIndex() < savedLastPos);
   2023             }
   2024         }
   2025         updateScrollLimits();
   2026         updateSecondaryScrollLimits();
   2027     }
   2028 
   2029     @Override
   2030     public void removeAndRecycleAllViews(RecyclerView.Recycler recycler) {
   2031         if (TRACE) TraceCompat.beginSection("removeAndRecycleAllViews");
   2032         if (DEBUG) Log.v(TAG, "removeAndRecycleAllViews " + getChildCount());
   2033         for (int i = getChildCount() - 1; i >= 0; i--) {
   2034             removeAndRecycleViewAt(i, recycler);
   2035         }
   2036         if (TRACE) TraceCompat.endSection();
   2037     }
   2038 
   2039     // called by onLayoutChildren, either focus to FocusPosition or declare focusViewAvailable
   2040     // and scroll to the view if framework focus on it.
   2041     private void focusToViewInLayout(boolean hadFocus, boolean alignToView, int extraDelta,
   2042             int extraDeltaSecondary) {
   2043         View focusView = findViewByPosition(mFocusPosition);
   2044         if (focusView != null && alignToView) {
   2045             scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
   2046         }
   2047         if (focusView != null && hadFocus && !focusView.hasFocus()) {
   2048             focusView.requestFocus();
   2049         } else if (!hadFocus && !mBaseGridView.hasFocus()) {
   2050             if (focusView != null && focusView.hasFocusable()) {
   2051                 mBaseGridView.focusableViewAvailable(focusView);
   2052             } else {
   2053                 for (int i = 0, count = getChildCount(); i < count; i++) {
   2054                     focusView = getChildAt(i);
   2055                     if (focusView != null && focusView.hasFocusable()) {
   2056                         mBaseGridView.focusableViewAvailable(focusView);
   2057                         break;
   2058                     }
   2059                 }
   2060             }
   2061             // focusViewAvailable() might focus to the view, scroll to it if that is the case.
   2062             if (alignToView && focusView != null && focusView.hasFocus()) {
   2063                 scrollToView(focusView, false, extraDelta, extraDeltaSecondary);
   2064             }
   2065         }
   2066     }
   2067 
   2068     @VisibleForTesting
   2069     public static class OnLayoutCompleteListener {
   2070         public void onLayoutCompleted(RecyclerView.State state) {
   2071         }
   2072     }
   2073 
   2074     @VisibleForTesting
   2075     OnLayoutCompleteListener mLayoutCompleteListener;
   2076 
   2077     @Override
   2078     public void onLayoutCompleted(State state) {
   2079         if (mLayoutCompleteListener != null) {
   2080             mLayoutCompleteListener.onLayoutCompleted(state);
   2081         }
   2082     }
   2083 
   2084     @Override
   2085     public boolean supportsPredictiveItemAnimations() {
   2086         return true;
   2087     }
   2088 
   2089     void updatePositionToRowMapInPostLayout() {
   2090         mPositionToRowInPostLayout.clear();
   2091         final int childCount = getChildCount();
   2092         for (int i = 0;  i < childCount; i++) {
   2093             // Grid still maps to old positions at this point, use old position to get row infor
   2094             int position = mBaseGridView.getChildViewHolder(getChildAt(i)).getOldPosition();
   2095             if (position >= 0) {
   2096                 Grid.Location loc = mGrid.getLocation(position);
   2097                 if (loc != null) {
   2098                     mPositionToRowInPostLayout.put(position, loc.row);
   2099                 }
   2100             }
   2101         }
   2102     }
   2103 
   2104     void fillScrapViewsInPostLayout() {
   2105         List<RecyclerView.ViewHolder> scrapList = mRecycler.getScrapList();
   2106         final int scrapSize = scrapList.size();
   2107         if (scrapSize == 0) {
   2108             return;
   2109         }
   2110         // initialize the int array or re-allocate the array.
   2111         if (mDisappearingPositions == null  || scrapSize > mDisappearingPositions.length) {
   2112             int length = mDisappearingPositions == null ? 16 : mDisappearingPositions.length;
   2113             while (length < scrapSize) {
   2114                 length = length << 1;
   2115             }
   2116             mDisappearingPositions = new int[length];
   2117         }
   2118         int totalItems = 0;
   2119         for (int i = 0; i < scrapSize; i++) {
   2120             int pos = scrapList.get(i).getAdapterPosition();
   2121             if (pos >= 0) {
   2122                 mDisappearingPositions[totalItems++] = pos;
   2123             }
   2124         }
   2125         // totalItems now has the length of disappearing items
   2126         if (totalItems > 0) {
   2127             Arrays.sort(mDisappearingPositions, 0, totalItems);
   2128             mGrid.fillDisappearingItems(mDisappearingPositions, totalItems,
   2129                     mPositionToRowInPostLayout);
   2130         }
   2131         mPositionToRowInPostLayout.clear();
   2132     }
   2133 
   2134     // in prelayout, first child's getViewPosition can be smaller than old adapter position
   2135     // if there were items removed before first visible index. For example:
   2136     // visible items are 3, 4, 5, 6, deleting 1, 2, 3 from adapter; the view position in
   2137     // prelayout are not 3(deleted), 4, 5, 6. Instead it's 1(deleted), 2, 3, 4.
   2138     // So there is a delta (2 in this case) between last cached position and prelayout position.
   2139     void updatePositionDeltaInPreLayout() {
   2140         if (getChildCount() > 0) {
   2141             View view = getChildAt(0);
   2142             LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2143             mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
   2144                     - lp.getViewLayoutPosition();
   2145         } else {
   2146             mPositionDeltaInPreLayout = 0;
   2147         }
   2148     }
   2149 
   2150     // Lays out items based on the current scroll position
   2151     @Override
   2152     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   2153         if (DEBUG) {
   2154             Log.v(getTag(), "layoutChildren start numRows " + mNumRows
   2155                     + " inPreLayout " + state.isPreLayout()
   2156                     + " didStructureChange " + state.didStructureChange()
   2157                     + " mForceFullLayout " + ((mFlag & PF_FORCE_FULL_LAYOUT) != 0));
   2158             Log.v(getTag(), "width " + getWidth() + " height " + getHeight());
   2159         }
   2160 
   2161         if (mNumRows == 0) {
   2162             // haven't done measure yet
   2163             return;
   2164         }
   2165         final int itemCount = state.getItemCount();
   2166         if (itemCount < 0) {
   2167             return;
   2168         }
   2169 
   2170         if ((mFlag & PF_SLIDING) != 0) {
   2171             // if there is already children, delay the layout process until slideIn(), if it's
   2172             // first time layout children: scroll them offscreen at end of onLayoutChildren()
   2173             if (getChildCount() > 0) {
   2174                 mFlag |= PF_LAYOUT_EATEN_IN_SLIDING;
   2175                 return;
   2176             }
   2177         }
   2178         if ((mFlag & PF_LAYOUT_ENABLED) == 0) {
   2179             discardLayoutInfo();
   2180             removeAndRecycleAllViews(recycler);
   2181             return;
   2182         }
   2183         mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_LAYOUT;
   2184 
   2185         saveContext(recycler, state);
   2186         if (state.isPreLayout()) {
   2187             updatePositionDeltaInPreLayout();
   2188             int childCount = getChildCount();
   2189             if (mGrid != null && childCount > 0) {
   2190                 int minChangedEdge = Integer.MAX_VALUE;
   2191                 int maxChangeEdge = Integer.MIN_VALUE;
   2192                 int minOldAdapterPosition = mBaseGridView.getChildViewHolder(
   2193                         getChildAt(0)).getOldPosition();
   2194                 int maxOldAdapterPosition = mBaseGridView.getChildViewHolder(
   2195                         getChildAt(childCount - 1)).getOldPosition();
   2196                 for (int i = 0; i < childCount; i++) {
   2197                     View view = getChildAt(i);
   2198                     LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2199                     int newAdapterPosition = mBaseGridView.getChildAdapterPosition(view);
   2200                     // if either of following happening
   2201                     // 1. item itself has changed or layout parameter changed
   2202                     // 2. item is losing focus
   2203                     // 3. item is gaining focus
   2204                     // 4. item is moved out of old adapter position range.
   2205                     if (lp.isItemChanged() || lp.isItemRemoved() || view.isLayoutRequested()
   2206                             || (!view.hasFocus() && mFocusPosition == lp.getViewAdapterPosition())
   2207                             || (view.hasFocus() && mFocusPosition != lp.getViewAdapterPosition())
   2208                             || newAdapterPosition < minOldAdapterPosition
   2209                             || newAdapterPosition > maxOldAdapterPosition) {
   2210                         minChangedEdge = Math.min(minChangedEdge, getViewMin(view));
   2211                         maxChangeEdge = Math.max(maxChangeEdge, getViewMax(view));
   2212                     }
   2213                 }
   2214                 if (maxChangeEdge > minChangedEdge) {
   2215                     mExtraLayoutSpaceInPreLayout = maxChangeEdge - minChangedEdge;
   2216                 }
   2217                 // append items for mExtraLayoutSpaceInPreLayout
   2218                 appendVisibleItems();
   2219                 prependVisibleItems();
   2220             }
   2221             mFlag &= ~PF_STAGE_MASK;
   2222             leaveContext();
   2223             if (DEBUG) Log.v(getTag(), "layoutChildren end");
   2224             return;
   2225         }
   2226 
   2227         // save all view's row information before detach all views
   2228         if (state.willRunPredictiveAnimations()) {
   2229             updatePositionToRowMapInPostLayout();
   2230         }
   2231         // check if we need align to mFocusPosition, this is usually true unless in smoothScrolling
   2232         final boolean scrollToFocus = !isSmoothScrolling()
   2233                 && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED;
   2234         if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
   2235             mFocusPosition = mFocusPosition + mFocusPositionOffset;
   2236             mSubFocusPosition = 0;
   2237         }
   2238         mFocusPositionOffset = 0;
   2239 
   2240         View savedFocusView = findViewByPosition(mFocusPosition);
   2241         int savedFocusPos = mFocusPosition;
   2242         int savedSubFocusPos = mSubFocusPosition;
   2243         boolean hadFocus = mBaseGridView.hasFocus();
   2244         final int firstVisibleIndex = mGrid != null ? mGrid.getFirstVisibleIndex() : NO_POSITION;
   2245         final int lastVisibleIndex = mGrid != null ? mGrid.getLastVisibleIndex() : NO_POSITION;
   2246         final int deltaPrimary;
   2247         final int deltaSecondary;
   2248         if (mOrientation == HORIZONTAL) {
   2249             deltaPrimary = state.getRemainingScrollHorizontal();
   2250             deltaSecondary = state.getRemainingScrollVertical();
   2251         } else {
   2252             deltaSecondary = state.getRemainingScrollHorizontal();
   2253             deltaPrimary = state.getRemainingScrollVertical();
   2254         }
   2255         if (layoutInit()) {
   2256             mFlag |= PF_FAST_RELAYOUT;
   2257             // If grid view is empty, we will start from mFocusPosition
   2258             mGrid.setStart(mFocusPosition);
   2259             fastRelayout();
   2260         } else {
   2261             mFlag &= ~PF_FAST_RELAYOUT;
   2262             // layoutInit() has detached all views, so start from scratch
   2263             mFlag = (mFlag & ~PF_IN_LAYOUT_SEARCH_FOCUS)
   2264                     | (hadFocus ? PF_IN_LAYOUT_SEARCH_FOCUS : 0);
   2265             int startFromPosition, endPos;
   2266             if (scrollToFocus && (firstVisibleIndex < 0 || mFocusPosition > lastVisibleIndex
   2267                     || mFocusPosition < firstVisibleIndex)) {
   2268                 startFromPosition = endPos = mFocusPosition;
   2269             } else {
   2270                 startFromPosition = firstVisibleIndex;
   2271                 endPos = lastVisibleIndex;
   2272             }
   2273             mGrid.setStart(startFromPosition);
   2274             if (endPos != NO_POSITION) {
   2275                 while (appendOneColumnVisibleItems() && findViewByPosition(endPos) == null) {
   2276                     // continuously append items until endPos
   2277                 }
   2278             }
   2279         }
   2280         // multiple rounds: scrollToView of first round may drag first/last child into
   2281         // "visible window" and we update scrollMin/scrollMax then run second scrollToView
   2282         // we must do this for fastRelayout() for the append item case
   2283         int oldFirstVisible;
   2284         int oldLastVisible;
   2285         do {
   2286             updateScrollLimits();
   2287             oldFirstVisible = mGrid.getFirstVisibleIndex();
   2288             oldLastVisible = mGrid.getLastVisibleIndex();
   2289             focusToViewInLayout(hadFocus, scrollToFocus, -deltaPrimary, -deltaSecondary);
   2290             appendVisibleItems();
   2291             prependVisibleItems();
   2292             // b/67370222: do not removeInvisibleViewsAtFront/End() in the loop, otherwise
   2293             // loop may bounce between scroll forward and scroll backward forever. Example:
   2294             // Assuming there are 19 items, child#18 and child#19 are both in RV, we are
   2295             // trying to focus to child#18 and there are 200px remaining scroll distance.
   2296             //   1  focusToViewInLayout() tries scroll forward 50 px to align focused child#18 on
   2297             //      right edge, but there to compensate remaining scroll 200px, also scroll
   2298             //      backward 200px, 150px pushes last child#19 out side of right edge.
   2299             //   2  removeInvisibleViewsAtEnd() remove last child#19, updateScrollLimits()
   2300             //      invalidates scroll max
   2301             //   3  In next iteration, when scroll max/min is unknown, focusToViewInLayout() will
   2302             //      align focused child#18 at center of screen.
   2303             //   4  Because #18 is aligned at center, appendVisibleItems() will fill child#19 to
   2304             //      the right.
   2305             //   5  (back to 1 and loop forever)
   2306         } while (mGrid.getFirstVisibleIndex() != oldFirstVisible
   2307                 || mGrid.getLastVisibleIndex() != oldLastVisible);
   2308         removeInvisibleViewsAtFront();
   2309         removeInvisibleViewsAtEnd();
   2310 
   2311         if (state.willRunPredictiveAnimations()) {
   2312             fillScrapViewsInPostLayout();
   2313         }
   2314 
   2315         if (DEBUG) {
   2316             StringWriter sw = new StringWriter();
   2317             PrintWriter pw = new PrintWriter(sw);
   2318             mGrid.debugPrint(pw);
   2319             Log.d(getTag(), sw.toString());
   2320         }
   2321 
   2322         if ((mFlag & PF_ROW_SECONDARY_SIZE_REFRESH) != 0) {
   2323             mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
   2324         } else {
   2325             updateRowSecondarySizeRefresh();
   2326         }
   2327 
   2328         // For fastRelayout, only dispatch event when focus position changes or selected item
   2329         // being updated.
   2330         if ((mFlag & PF_FAST_RELAYOUT) != 0 && (mFocusPosition != savedFocusPos || mSubFocusPosition
   2331                 != savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView
   2332                 || (mFlag & PF_FAST_RELAYOUT_UPDATED_SELECTED_POSITION) != 0)) {
   2333             dispatchChildSelected();
   2334         } else if ((mFlag & (PF_FAST_RELAYOUT | PF_IN_LAYOUT_SEARCH_FOCUS))
   2335                 == PF_IN_LAYOUT_SEARCH_FOCUS) {
   2336             // For full layout we dispatchChildSelected() in createItem() unless searched all
   2337             // children and found none is focusable then dispatchChildSelected() here.
   2338             dispatchChildSelected();
   2339         }
   2340         dispatchChildSelectedAndPositioned();
   2341         if ((mFlag & PF_SLIDING) != 0) {
   2342             scrollDirectionPrimary(getSlideOutDistance());
   2343         }
   2344 
   2345         mFlag &= ~PF_STAGE_MASK;
   2346         leaveContext();
   2347         if (DEBUG) Log.v(getTag(), "layoutChildren end");
   2348     }
   2349 
   2350     private void offsetChildrenSecondary(int increment) {
   2351         final int childCount = getChildCount();
   2352         if (mOrientation == HORIZONTAL) {
   2353             for (int i = 0; i < childCount; i++) {
   2354                 getChildAt(i).offsetTopAndBottom(increment);
   2355             }
   2356         } else {
   2357             for (int i = 0; i < childCount; i++) {
   2358                 getChildAt(i).offsetLeftAndRight(increment);
   2359             }
   2360         }
   2361     }
   2362 
   2363     private void offsetChildrenPrimary(int increment) {
   2364         final int childCount = getChildCount();
   2365         if (mOrientation == VERTICAL) {
   2366             for (int i = 0; i < childCount; i++) {
   2367                 getChildAt(i).offsetTopAndBottom(increment);
   2368             }
   2369         } else {
   2370             for (int i = 0; i < childCount; i++) {
   2371                 getChildAt(i).offsetLeftAndRight(increment);
   2372             }
   2373         }
   2374     }
   2375 
   2376     @Override
   2377     public int scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state) {
   2378         if (DEBUG) Log.v(getTag(), "scrollHorizontallyBy " + dx);
   2379         if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
   2380             return 0;
   2381         }
   2382         saveContext(recycler, state);
   2383         mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
   2384         int result;
   2385         if (mOrientation == HORIZONTAL) {
   2386             result = scrollDirectionPrimary(dx);
   2387         } else {
   2388             result = scrollDirectionSecondary(dx);
   2389         }
   2390         leaveContext();
   2391         mFlag &= ~PF_STAGE_MASK;
   2392         return result;
   2393     }
   2394 
   2395     @Override
   2396     public int scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state) {
   2397         if (DEBUG) Log.v(getTag(), "scrollVerticallyBy " + dy);
   2398         if ((mFlag & PF_LAYOUT_ENABLED) == 0 || !hasDoneFirstLayout()) {
   2399             return 0;
   2400         }
   2401         mFlag = (mFlag & ~PF_STAGE_MASK) | PF_STAGE_SCROLL;
   2402         saveContext(recycler, state);
   2403         int result;
   2404         if (mOrientation == VERTICAL) {
   2405             result = scrollDirectionPrimary(dy);
   2406         } else {
   2407             result = scrollDirectionSecondary(dy);
   2408         }
   2409         leaveContext();
   2410         mFlag &= ~PF_STAGE_MASK;
   2411         return result;
   2412     }
   2413 
   2414     // scroll in main direction may add/prune views
   2415     private int scrollDirectionPrimary(int da) {
   2416         if (TRACE) TraceCompat.beginSection("scrollPrimary");
   2417         // We apply the cap of maxScroll/minScroll to the delta, except for two cases:
   2418         // 1. when children are in sliding out mode
   2419         // 2. During onLayoutChildren(), it may compensate the remaining scroll delta,
   2420         //    we should honor the request regardless if it goes over minScroll / maxScroll.
   2421         //    (see b/64931938 testScrollAndRemove and testScrollAndRemoveSample1)
   2422         if ((mFlag & PF_SLIDING) == 0 && (mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
   2423             if (da > 0) {
   2424                 if (!mWindowAlignment.mainAxis().isMaxUnknown()) {
   2425                     int maxScroll = mWindowAlignment.mainAxis().getMaxScroll();
   2426                     if (da > maxScroll) {
   2427                         da = maxScroll;
   2428                     }
   2429                 }
   2430             } else if (da < 0) {
   2431                 if (!mWindowAlignment.mainAxis().isMinUnknown()) {
   2432                     int minScroll = mWindowAlignment.mainAxis().getMinScroll();
   2433                     if (da < minScroll) {
   2434                         da = minScroll;
   2435                     }
   2436                 }
   2437             }
   2438         }
   2439         if (da == 0) {
   2440             if (TRACE) TraceCompat.endSection();
   2441             return 0;
   2442         }
   2443         offsetChildrenPrimary(-da);
   2444         if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
   2445             updateScrollLimits();
   2446             if (TRACE) TraceCompat.endSection();
   2447             return da;
   2448         }
   2449 
   2450         int childCount = getChildCount();
   2451         boolean updated;
   2452 
   2453         if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
   2454             prependVisibleItems();
   2455         } else {
   2456             appendVisibleItems();
   2457         }
   2458         updated = getChildCount() > childCount;
   2459         childCount = getChildCount();
   2460 
   2461         if (TRACE) TraceCompat.beginSection("remove");
   2462         if ((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0 ? da > 0 : da < 0) {
   2463             removeInvisibleViewsAtEnd();
   2464         } else {
   2465             removeInvisibleViewsAtFront();
   2466         }
   2467         if (TRACE) TraceCompat.endSection();
   2468         updated |= getChildCount() < childCount;
   2469         if (updated) {
   2470             updateRowSecondarySizeRefresh();
   2471         }
   2472 
   2473         mBaseGridView.invalidate();
   2474         updateScrollLimits();
   2475         if (TRACE) TraceCompat.endSection();
   2476         return da;
   2477     }
   2478 
   2479     // scroll in second direction will not add/prune views
   2480     private int scrollDirectionSecondary(int dy) {
   2481         if (dy == 0) {
   2482             return 0;
   2483         }
   2484         offsetChildrenSecondary(-dy);
   2485         mScrollOffsetSecondary += dy;
   2486         updateSecondaryScrollLimits();
   2487         mBaseGridView.invalidate();
   2488         return dy;
   2489     }
   2490 
   2491     @Override
   2492     public void collectAdjacentPrefetchPositions(int dx, int dy, State state,
   2493             LayoutPrefetchRegistry layoutPrefetchRegistry) {
   2494         try {
   2495             saveContext(null, state);
   2496             int da = (mOrientation == HORIZONTAL) ? dx : dy;
   2497             if (getChildCount() == 0 || da == 0) {
   2498                 // can't support this scroll, so don't bother prefetching
   2499                 return;
   2500             }
   2501 
   2502             int fromLimit = da < 0
   2503                     ? -mExtraLayoutSpace
   2504                     : mSizePrimary + mExtraLayoutSpace;
   2505             mGrid.collectAdjacentPrefetchPositions(fromLimit, da, layoutPrefetchRegistry);
   2506         } finally {
   2507             leaveContext();
   2508         }
   2509     }
   2510 
   2511     @Override
   2512     public void collectInitialPrefetchPositions(int adapterItemCount,
   2513             LayoutPrefetchRegistry layoutPrefetchRegistry) {
   2514         int numToPrefetch = mBaseGridView.mInitialPrefetchItemCount;
   2515         if (adapterItemCount != 0 && numToPrefetch != 0) {
   2516             // prefetch items centered around mFocusPosition
   2517             int initialPos = Math.max(0, Math.min(mFocusPosition - (numToPrefetch - 1)/ 2,
   2518                     adapterItemCount - numToPrefetch));
   2519             for (int i = initialPos; i < adapterItemCount && i < initialPos + numToPrefetch; i++) {
   2520                 layoutPrefetchRegistry.addPosition(i, 0);
   2521             }
   2522         }
   2523     }
   2524 
   2525     void updateScrollLimits() {
   2526         if (mState.getItemCount() == 0) {
   2527             return;
   2528         }
   2529         int highVisiblePos, lowVisiblePos;
   2530         int highMaxPos, lowMinPos;
   2531         if ((mFlag & PF_REVERSE_FLOW_PRIMARY) == 0) {
   2532             highVisiblePos = mGrid.getLastVisibleIndex();
   2533             highMaxPos = mState.getItemCount() - 1;
   2534             lowVisiblePos = mGrid.getFirstVisibleIndex();
   2535             lowMinPos = 0;
   2536         } else {
   2537             highVisiblePos = mGrid.getFirstVisibleIndex();
   2538             highMaxPos = 0;
   2539             lowVisiblePos = mGrid.getLastVisibleIndex();
   2540             lowMinPos = mState.getItemCount() - 1;
   2541         }
   2542         if (highVisiblePos < 0 || lowVisiblePos < 0) {
   2543             return;
   2544         }
   2545         final boolean highAvailable = highVisiblePos == highMaxPos;
   2546         final boolean lowAvailable = lowVisiblePos == lowMinPos;
   2547         if (!highAvailable && mWindowAlignment.mainAxis().isMaxUnknown()
   2548                 && !lowAvailable && mWindowAlignment.mainAxis().isMinUnknown()) {
   2549             return;
   2550         }
   2551         int maxEdge, maxViewCenter;
   2552         if (highAvailable) {
   2553             maxEdge = mGrid.findRowMax(true, sTwoInts);
   2554             View maxChild = findViewByPosition(sTwoInts[1]);
   2555             maxViewCenter = getViewCenter(maxChild);
   2556             final LayoutParams lp = (LayoutParams) maxChild.getLayoutParams();
   2557             int[] multipleAligns = lp.getAlignMultiple();
   2558             if (multipleAligns != null && multipleAligns.length > 0) {
   2559                 maxViewCenter += multipleAligns[multipleAligns.length - 1] - multipleAligns[0];
   2560             }
   2561         } else {
   2562             maxEdge = Integer.MAX_VALUE;
   2563             maxViewCenter = Integer.MAX_VALUE;
   2564         }
   2565         int minEdge, minViewCenter;
   2566         if (lowAvailable) {
   2567             minEdge = mGrid.findRowMin(false, sTwoInts);
   2568             View minChild = findViewByPosition(sTwoInts[1]);
   2569             minViewCenter = getViewCenter(minChild);
   2570         } else {
   2571             minEdge = Integer.MIN_VALUE;
   2572             minViewCenter = Integer.MIN_VALUE;
   2573         }
   2574         mWindowAlignment.mainAxis().updateMinMax(minEdge, maxEdge, minViewCenter, maxViewCenter);
   2575     }
   2576 
   2577     /**
   2578      * Update secondary axis's scroll min/max, should be updated in
   2579      * {@link #scrollDirectionSecondary(int)}.
   2580      */
   2581     private void updateSecondaryScrollLimits() {
   2582         WindowAlignment.Axis secondAxis = mWindowAlignment.secondAxis();
   2583         int minEdge = secondAxis.getPaddingMin() - mScrollOffsetSecondary;
   2584         int maxEdge = minEdge + getSizeSecondary();
   2585         secondAxis.updateMinMax(minEdge, maxEdge, minEdge, maxEdge);
   2586     }
   2587 
   2588     private void initScrollController() {
   2589         mWindowAlignment.reset();
   2590         mWindowAlignment.horizontal.setSize(getWidth());
   2591         mWindowAlignment.vertical.setSize(getHeight());
   2592         mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
   2593         mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
   2594         mSizePrimary = mWindowAlignment.mainAxis().getSize();
   2595         mScrollOffsetSecondary = 0;
   2596 
   2597         if (DEBUG) {
   2598             Log.v(getTag(), "initScrollController mSizePrimary " + mSizePrimary
   2599                     + " mWindowAlignment " + mWindowAlignment);
   2600         }
   2601     }
   2602 
   2603     private void updateScrollController() {
   2604         mWindowAlignment.horizontal.setSize(getWidth());
   2605         mWindowAlignment.vertical.setSize(getHeight());
   2606         mWindowAlignment.horizontal.setPadding(getPaddingLeft(), getPaddingRight());
   2607         mWindowAlignment.vertical.setPadding(getPaddingTop(), getPaddingBottom());
   2608         mSizePrimary = mWindowAlignment.mainAxis().getSize();
   2609 
   2610         if (DEBUG) {
   2611             Log.v(getTag(), "updateScrollController mSizePrimary " + mSizePrimary
   2612                     + " mWindowAlignment " + mWindowAlignment);
   2613         }
   2614     }
   2615 
   2616     @Override
   2617     public void scrollToPosition(int position) {
   2618         setSelection(position, 0, false, 0);
   2619     }
   2620 
   2621     @Override
   2622     public void smoothScrollToPosition(RecyclerView recyclerView, State state,
   2623             int position) {
   2624         setSelection(position, 0, true, 0);
   2625     }
   2626 
   2627     public void setSelection(int position,
   2628             int primaryScrollExtra) {
   2629         setSelection(position, 0, false, primaryScrollExtra);
   2630     }
   2631 
   2632     public void setSelectionSmooth(int position) {
   2633         setSelection(position, 0, true, 0);
   2634     }
   2635 
   2636     public void setSelectionWithSub(int position, int subposition,
   2637             int primaryScrollExtra) {
   2638         setSelection(position, subposition, false, primaryScrollExtra);
   2639     }
   2640 
   2641     public void setSelectionSmoothWithSub(int position, int subposition) {
   2642         setSelection(position, subposition, true, 0);
   2643     }
   2644 
   2645     public int getSelection() {
   2646         return mFocusPosition;
   2647     }
   2648 
   2649     public int getSubSelection() {
   2650         return mSubFocusPosition;
   2651     }
   2652 
   2653     public void setSelection(int position, int subposition, boolean smooth,
   2654             int primaryScrollExtra) {
   2655         if ((mFocusPosition != position && position != NO_POSITION)
   2656                 || subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
   2657             scrollToSelection(position, subposition, smooth, primaryScrollExtra);
   2658         }
   2659     }
   2660 
   2661     void scrollToSelection(int position, int subposition,
   2662             boolean smooth, int primaryScrollExtra) {
   2663         if (TRACE) TraceCompat.beginSection("scrollToSelection");
   2664         mPrimaryScrollExtra = primaryScrollExtra;
   2665 
   2666         View view = findViewByPosition(position);
   2667         // scrollToView() is based on Adapter position. Only call scrollToView() when item
   2668         // is still valid and no layout is requested, otherwise defer to next layout pass.
   2669         // If it is still in smoothScrolling, we should either update smoothScroller or initiate
   2670         // a layout.
   2671         final boolean notSmoothScrolling = !isSmoothScrolling();
   2672         if (notSmoothScrolling && !mBaseGridView.isLayoutRequested()
   2673                 && view != null && getAdapterPositionByView(view) == position) {
   2674             mFlag |= PF_IN_SELECTION;
   2675             scrollToView(view, smooth);
   2676             mFlag &= ~PF_IN_SELECTION;
   2677         } else {
   2678             if ((mFlag & PF_LAYOUT_ENABLED) == 0 || (mFlag & PF_SLIDING) != 0) {
   2679                 mFocusPosition = position;
   2680                 mSubFocusPosition = subposition;
   2681                 mFocusPositionOffset = Integer.MIN_VALUE;
   2682                 return;
   2683             }
   2684             if (smooth && !mBaseGridView.isLayoutRequested()) {
   2685                 mFocusPosition = position;
   2686                 mSubFocusPosition = subposition;
   2687                 mFocusPositionOffset = Integer.MIN_VALUE;
   2688                 if (!hasDoneFirstLayout()) {
   2689                     Log.w(getTag(), "setSelectionSmooth should "
   2690                             + "not be called before first layout pass");
   2691                     return;
   2692                 }
   2693                 position = startPositionSmoothScroller(position);
   2694                 if (position != mFocusPosition) {
   2695                     // gets cropped by adapter size
   2696                     mFocusPosition = position;
   2697                     mSubFocusPosition = 0;
   2698                 }
   2699             } else {
   2700                 // stopScroll might change mFocusPosition, so call it before assign value to
   2701                 // mFocusPosition
   2702                 if (!notSmoothScrolling) {
   2703                     skipSmoothScrollerOnStopInternal();
   2704                     mBaseGridView.stopScroll();
   2705                 }
   2706                 if (!mBaseGridView.isLayoutRequested()
   2707                         && view != null && getAdapterPositionByView(view) == position) {
   2708                     mFlag |= PF_IN_SELECTION;
   2709                     scrollToView(view, smooth);
   2710                     mFlag &= ~PF_IN_SELECTION;
   2711                 } else {
   2712                     mFocusPosition = position;
   2713                     mSubFocusPosition = subposition;
   2714                     mFocusPositionOffset = Integer.MIN_VALUE;
   2715                     mFlag |= PF_FORCE_FULL_LAYOUT;
   2716                     requestLayout();
   2717                 }
   2718             }
   2719         }
   2720         if (TRACE) TraceCompat.endSection();
   2721     }
   2722 
   2723     int startPositionSmoothScroller(int position) {
   2724         LinearSmoothScroller linearSmoothScroller = new GridLinearSmoothScroller() {
   2725             @Override
   2726             public PointF computeScrollVectorForPosition(int targetPosition) {
   2727                 if (getChildCount() == 0) {
   2728                     return null;
   2729                 }
   2730                 final int firstChildPos = getPosition(getChildAt(0));
   2731                 // TODO We should be able to deduce direction from bounds of current and target
   2732                 // focus, rather than making assumptions about positions and directionality
   2733                 final boolean isStart = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
   2734                         ? targetPosition > firstChildPos
   2735                         : targetPosition < firstChildPos;
   2736                 final int direction = isStart ? -1 : 1;
   2737                 if (mOrientation == HORIZONTAL) {
   2738                     return new PointF(direction, 0);
   2739                 } else {
   2740                     return new PointF(0, direction);
   2741                 }
   2742             }
   2743 
   2744         };
   2745         linearSmoothScroller.setTargetPosition(position);
   2746         startSmoothScroll(linearSmoothScroller);
   2747         return linearSmoothScroller.getTargetPosition();
   2748     }
   2749 
   2750     /**
   2751      * when start a new SmoothScroller or scroll to a different location, dont need
   2752      * current SmoothScroller.onStopInternal() doing the scroll work.
   2753      */
   2754     void skipSmoothScrollerOnStopInternal() {
   2755         if (mCurrentSmoothScroller != null) {
   2756             mCurrentSmoothScroller.mSkipOnStopInternal = true;
   2757         }
   2758     }
   2759 
   2760     @Override
   2761     public void startSmoothScroll(RecyclerView.SmoothScroller smoothScroller) {
   2762         skipSmoothScrollerOnStopInternal();
   2763         super.startSmoothScroll(smoothScroller);
   2764         if (smoothScroller.isRunning() && smoothScroller instanceof GridLinearSmoothScroller) {
   2765             mCurrentSmoothScroller = (GridLinearSmoothScroller) smoothScroller;
   2766             if (mCurrentSmoothScroller instanceof PendingMoveSmoothScroller) {
   2767                 mPendingMoveSmoothScroller = (PendingMoveSmoothScroller) mCurrentSmoothScroller;
   2768             } else {
   2769                 mPendingMoveSmoothScroller = null;
   2770             }
   2771         } else {
   2772             mCurrentSmoothScroller = null;
   2773             mPendingMoveSmoothScroller = null;
   2774         }
   2775     }
   2776 
   2777     private void processPendingMovement(boolean forward) {
   2778         if (forward ? hasCreatedLastItem() : hasCreatedFirstItem()) {
   2779             return;
   2780         }
   2781         if (mPendingMoveSmoothScroller == null) {
   2782             // Stop existing scroller and create a new PendingMoveSmoothScroller.
   2783             mBaseGridView.stopScroll();
   2784             PendingMoveSmoothScroller linearSmoothScroller = new PendingMoveSmoothScroller(
   2785                     forward ? 1 : -1, mNumRows > 1);
   2786             mFocusPositionOffset = 0;
   2787             startSmoothScroll(linearSmoothScroller);
   2788         } else {
   2789             if (forward) {
   2790                 mPendingMoveSmoothScroller.increasePendingMoves();
   2791             } else {
   2792                 mPendingMoveSmoothScroller.decreasePendingMoves();
   2793             }
   2794         }
   2795     }
   2796 
   2797     @Override
   2798     public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
   2799         if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
   2800                 + positionStart + " itemCount " + itemCount);
   2801         if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
   2802                 && mFocusPositionOffset != Integer.MIN_VALUE) {
   2803             int pos = mFocusPosition + mFocusPositionOffset;
   2804             if (positionStart <= pos) {
   2805                 mFocusPositionOffset += itemCount;
   2806             }
   2807         }
   2808         mChildrenStates.clear();
   2809     }
   2810 
   2811     @Override
   2812     public void onItemsChanged(RecyclerView recyclerView) {
   2813         if (DEBUG) Log.v(getTag(), "onItemsChanged");
   2814         mFocusPositionOffset = 0;
   2815         mChildrenStates.clear();
   2816     }
   2817 
   2818     @Override
   2819     public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
   2820         if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
   2821                 + positionStart + " itemCount " + itemCount);
   2822         if (mFocusPosition != NO_POSITION  && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
   2823                 && mFocusPositionOffset != Integer.MIN_VALUE) {
   2824             int pos = mFocusPosition + mFocusPositionOffset;
   2825             if (positionStart <= pos) {
   2826                 if (positionStart + itemCount > pos) {
   2827                     // stop updating offset after the focus item was removed
   2828                     mFocusPositionOffset += positionStart - pos;
   2829                     mFocusPosition += mFocusPositionOffset;
   2830                     mFocusPositionOffset = Integer.MIN_VALUE;
   2831                 } else {
   2832                     mFocusPositionOffset -= itemCount;
   2833                 }
   2834             }
   2835         }
   2836         mChildrenStates.clear();
   2837     }
   2838 
   2839     @Override
   2840     public void onItemsMoved(RecyclerView recyclerView, int fromPosition, int toPosition,
   2841             int itemCount) {
   2842         if (DEBUG) Log.v(getTag(), "onItemsMoved fromPosition "
   2843                 + fromPosition + " toPosition " + toPosition);
   2844         if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
   2845             int pos = mFocusPosition + mFocusPositionOffset;
   2846             if (fromPosition <= pos && pos < fromPosition + itemCount) {
   2847                 // moved items include focused position
   2848                 mFocusPositionOffset += toPosition - fromPosition;
   2849             } else if (fromPosition < pos && toPosition > pos - itemCount) {
   2850                 // move items before focus position to after focused position
   2851                 mFocusPositionOffset -= itemCount;
   2852             } else if (fromPosition > pos && toPosition < pos) {
   2853                 // move items after focus position to before focused position
   2854                 mFocusPositionOffset += itemCount;
   2855             }
   2856         }
   2857         mChildrenStates.clear();
   2858     }
   2859 
   2860     @Override
   2861     public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
   2862         if (DEBUG) Log.v(getTag(), "onItemsUpdated positionStart "
   2863                 + positionStart + " itemCount " + itemCount);
   2864         for (int i = positionStart, end = positionStart + itemCount; i < end; i++) {
   2865             mChildrenStates.remove(i);
   2866         }
   2867     }
   2868 
   2869     @Override
   2870     public boolean onRequestChildFocus(RecyclerView parent, View child, View focused) {
   2871         if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
   2872             return true;
   2873         }
   2874         if (getAdapterPositionByView(child) == NO_POSITION) {
   2875             // This is could be the last view in DISAPPEARING animation.
   2876             return true;
   2877         }
   2878         if ((mFlag & (PF_STAGE_MASK | PF_IN_SELECTION)) == 0) {
   2879             scrollToView(child, focused, true);
   2880         }
   2881         return true;
   2882     }
   2883 
   2884     @Override
   2885     public boolean requestChildRectangleOnScreen(RecyclerView parent, View view, Rect rect,
   2886             boolean immediate) {
   2887         if (DEBUG) Log.v(getTag(), "requestChildRectangleOnScreen " + view + " " + rect);
   2888         return false;
   2889     }
   2890 
   2891     public void getViewSelectedOffsets(View view, int[] offsets) {
   2892         if (mOrientation == HORIZONTAL) {
   2893             offsets[0] = getPrimaryAlignedScrollDistance(view);
   2894             offsets[1] = getSecondaryScrollDistance(view);
   2895         } else {
   2896             offsets[1] = getPrimaryAlignedScrollDistance(view);
   2897             offsets[0] = getSecondaryScrollDistance(view);
   2898         }
   2899     }
   2900 
   2901     /**
   2902      * Return the scroll delta on primary direction to make the view selected. If the return value
   2903      * is 0, there is no need to scroll.
   2904      */
   2905     private int getPrimaryAlignedScrollDistance(View view) {
   2906         return mWindowAlignment.mainAxis().getScroll(getViewCenter(view));
   2907     }
   2908 
   2909     /**
   2910      * Get adjusted primary position for a given childView (if there is multiple ItemAlignment
   2911      * defined on the view).
   2912      */
   2913     private int getAdjustedPrimaryAlignedScrollDistance(int scrollPrimary, View view,
   2914             View childView) {
   2915         int subindex = getSubPositionByView(view, childView);
   2916         if (subindex != 0) {
   2917             final LayoutParams lp = (LayoutParams) view.getLayoutParams();
   2918             scrollPrimary += lp.getAlignMultiple()[subindex] - lp.getAlignMultiple()[0];
   2919         }
   2920         return scrollPrimary;
   2921     }
   2922 
   2923     private int getSecondaryScrollDistance(View view) {
   2924         int viewCenterSecondary = getViewCenterSecondary(view);
   2925         return mWindowAlignment.secondAxis().getScroll(viewCenterSecondary);
   2926     }
   2927 
   2928     /**
   2929      * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
   2930      */
   2931     void scrollToView(View view, boolean smooth) {
   2932         scrollToView(view, view == null ? null : view.findFocus(), smooth);
   2933     }
   2934 
   2935     void scrollToView(View view, boolean smooth, int extraDelta, int extraDeltaSecondary) {
   2936         scrollToView(view, view == null ? null : view.findFocus(), smooth, extraDelta,
   2937                 extraDeltaSecondary);
   2938     }
   2939 
   2940     private void scrollToView(View view, View childView, boolean smooth) {
   2941         scrollToView(view, childView, smooth, 0, 0);
   2942     }
   2943     /**
   2944      * Scroll to a given child view and change mFocusPosition. Ignored when in slideOut() state.
   2945      */
   2946     private void scrollToView(View view, View childView, boolean smooth, int extraDelta,
   2947             int extraDeltaSecondary) {
   2948         if ((mFlag & PF_SLIDING) != 0) {
   2949             return;
   2950         }
   2951         int newFocusPosition = getAdapterPositionByView(view);
   2952         int newSubFocusPosition = getSubPositionByView(view, childView);
   2953         if (newFocusPosition != mFocusPosition || newSubFocusPosition != mSubFocusPosition) {
   2954             mFocusPosition = newFocusPosition;
   2955             mSubFocusPosition = newSubFocusPosition;
   2956             mFocusPositionOffset = 0;
   2957             if ((mFlag & PF_STAGE_MASK) != PF_STAGE_LAYOUT) {
   2958                 dispatchChildSelected();
   2959             }
   2960             if (mBaseGridView.isChildrenDrawingOrderEnabledInternal()) {
   2961                 mBaseGridView.invalidate();
   2962             }
   2963         }
   2964         if (view == null) {
   2965             return;
   2966         }
   2967         if (!view.hasFocus() && mBaseGridView.hasFocus()) {
   2968             // transfer focus to the child if it does not have focus yet (e.g. triggered
   2969             // by setSelection())
   2970             view.requestFocus();
   2971         }
   2972         if ((mFlag & PF_SCROLL_ENABLED) == 0 && smooth) {
   2973             return;
   2974         }
   2975         if (getScrollPosition(view, childView, sTwoInts)
   2976                 || extraDelta != 0 || extraDeltaSecondary != 0) {
   2977             scrollGrid(sTwoInts[0] + extraDelta, sTwoInts[1] + extraDeltaSecondary, smooth);
   2978         }
   2979     }
   2980 
   2981     boolean getScrollPosition(View view, View childView, int[] deltas) {
   2982         switch (mFocusScrollStrategy) {
   2983             case BaseGridView.FOCUS_SCROLL_ALIGNED:
   2984             default:
   2985                 return getAlignedPosition(view, childView, deltas);
   2986             case BaseGridView.FOCUS_SCROLL_ITEM:
   2987             case BaseGridView.FOCUS_SCROLL_PAGE:
   2988                 return getNoneAlignedPosition(view, deltas);
   2989         }
   2990     }
   2991 
   2992     private boolean getNoneAlignedPosition(View view, int[] deltas) {
   2993         int pos = getAdapterPositionByView(view);
   2994         int viewMin = getViewMin(view);
   2995         int viewMax = getViewMax(view);
   2996         // we either align "firstView" to left/top padding edge
   2997         // or align "lastView" to right/bottom padding edge
   2998         View firstView = null;
   2999         View lastView = null;
   3000         int paddingMin = mWindowAlignment.mainAxis().getPaddingMin();
   3001         int clientSize = mWindowAlignment.mainAxis().getClientSize();
   3002         final int row = mGrid.getRowIndex(pos);
   3003         if (viewMin < paddingMin) {
   3004             // view enters low padding area:
   3005             firstView = view;
   3006             if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
   3007                 // scroll one "page" left/top,
   3008                 // align first visible item of the "page" at the low padding edge.
   3009                 while (prependOneColumnVisibleItems()) {
   3010                     CircularIntArray positions =
   3011                             mGrid.getItemPositionsInRows(mGrid.getFirstVisibleIndex(), pos)[row];
   3012                     firstView = findViewByPosition(positions.get(0));
   3013                     if (viewMax - getViewMin(firstView) > clientSize) {
   3014                         if (positions.size() > 2) {
   3015                             firstView = findViewByPosition(positions.get(2));
   3016                         }
   3017                         break;
   3018                     }
   3019                 }
   3020             }
   3021         } else if (viewMax > clientSize + paddingMin) {
   3022             // view enters high padding area:
   3023             if (mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_PAGE) {
   3024                 // scroll whole one page right/bottom, align view at the low padding edge.
   3025                 firstView = view;
   3026                 do {
   3027                     CircularIntArray positions =
   3028                             mGrid.getItemPositionsInRows(pos, mGrid.getLastVisibleIndex())[row];
   3029                     lastView = findViewByPosition(positions.get(positions.size() - 1));
   3030                     if (getViewMax(lastView) - viewMin > clientSize) {
   3031                         lastView = null;
   3032                         break;
   3033                     }
   3034                 } while (appendOneColumnVisibleItems());
   3035                 if (lastView != null) {
   3036                     // however if we reached end,  we should align last view.
   3037                     firstView = null;
   3038                 }
   3039             } else {
   3040                 lastView = view;
   3041             }
   3042         }
   3043         int scrollPrimary = 0;
   3044         int scrollSecondary = 0;
   3045         if (firstView != null) {
   3046             scrollPrimary = getViewMin(firstView) - paddingMin;
   3047         } else if (lastView != null) {
   3048             scrollPrimary = getViewMax(lastView) - (paddingMin + clientSize);
   3049         }
   3050         View secondaryAlignedView;
   3051         if (firstView != null) {
   3052             secondaryAlignedView = firstView;
   3053         } else if (lastView != null) {
   3054             secondaryAlignedView = lastView;
   3055         } else {
   3056             secondaryAlignedView = view;
   3057         }
   3058         scrollSecondary = getSecondaryScrollDistance(secondaryAlignedView);
   3059         if (scrollPrimary != 0 || scrollSecondary != 0) {
   3060             deltas[0] = scrollPrimary;
   3061             deltas[1] = scrollSecondary;
   3062             return true;
   3063         }
   3064         return false;
   3065     }
   3066 
   3067     private boolean getAlignedPosition(View view, View childView, int[] deltas) {
   3068         int scrollPrimary = getPrimaryAlignedScrollDistance(view);
   3069         if (childView != null) {
   3070             scrollPrimary = getAdjustedPrimaryAlignedScrollDistance(scrollPrimary, view, childView);
   3071         }
   3072         int scrollSecondary = getSecondaryScrollDistance(view);
   3073         if (DEBUG) {
   3074             Log.v(getTag(), "getAlignedPosition " + scrollPrimary + " " + scrollSecondary
   3075                     + " " + mPrimaryScrollExtra + " " + mWindowAlignment);
   3076         }
   3077         scrollPrimary += mPrimaryScrollExtra;
   3078         if (scrollPrimary != 0 || scrollSecondary != 0) {
   3079             deltas[0] = scrollPrimary;
   3080             deltas[1] = scrollSecondary;
   3081             return true;
   3082         } else {
   3083             deltas[0] = 0;
   3084             deltas[1] = 0;
   3085         }
   3086         return false;
   3087     }
   3088 
   3089     private void scrollGrid(int scrollPrimary, int scrollSecondary, boolean smooth) {
   3090         if ((mFlag & PF_STAGE_MASK) == PF_STAGE_LAYOUT) {
   3091             scrollDirectionPrimary(scrollPrimary);
   3092             scrollDirectionSecondary(scrollSecondary);
   3093         } else {
   3094             int scrollX;
   3095             int scrollY;
   3096             if (mOrientation == HORIZONTAL) {
   3097                 scrollX = scrollPrimary;
   3098                 scrollY = scrollSecondary;
   3099             } else {
   3100                 scrollX = scrollSecondary;
   3101                 scrollY = scrollPrimary;
   3102             }
   3103             if (smooth) {
   3104                 mBaseGridView.smoothScrollBy(scrollX, scrollY);
   3105             } else {
   3106                 mBaseGridView.scrollBy(scrollX, scrollY);
   3107                 dispatchChildSelectedAndPositioned();
   3108             }
   3109         }
   3110     }
   3111 
   3112     public void setPruneChild(boolean pruneChild) {
   3113         if (((mFlag & PF_PRUNE_CHILD) != 0) != pruneChild) {
   3114             mFlag = (mFlag & ~PF_PRUNE_CHILD) | (pruneChild ? PF_PRUNE_CHILD : 0);
   3115             if (pruneChild) {
   3116                 requestLayout();
   3117             }
   3118         }
   3119     }
   3120 
   3121     public boolean getPruneChild() {
   3122         return (mFlag & PF_PRUNE_CHILD) != 0;
   3123     }
   3124 
   3125     public void setScrollEnabled(boolean scrollEnabled) {
   3126         if (((mFlag & PF_SCROLL_ENABLED) != 0) != scrollEnabled) {
   3127             mFlag = (mFlag & ~PF_SCROLL_ENABLED) | (scrollEnabled ? PF_SCROLL_ENABLED : 0);
   3128             if (((mFlag & PF_SCROLL_ENABLED) != 0)
   3129                     && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
   3130                     && mFocusPosition != NO_POSITION) {
   3131                 scrollToSelection(mFocusPosition, mSubFocusPosition,
   3132                         true, mPrimaryScrollExtra);
   3133             }
   3134         }
   3135     }
   3136 
   3137     public boolean isScrollEnabled() {
   3138         return (mFlag & PF_SCROLL_ENABLED) != 0;
   3139     }
   3140 
   3141     private int findImmediateChildIndex(View view) {
   3142         if (mBaseGridView != null && view != mBaseGridView) {
   3143             view = findContainingItemView(view);
   3144             if (view != null) {
   3145                 for (int i = 0, count = getChildCount(); i < count; i++) {
   3146                     if (getChildAt(i) == view) {
   3147                         return i;
   3148                     }
   3149                 }
   3150             }
   3151         }
   3152         return NO_POSITION;
   3153     }
   3154 
   3155     void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
   3156         if (gainFocus) {
   3157             // if gridview.requestFocus() is called, select first focusable child.
   3158             for (int i = mFocusPosition; ;i++) {
   3159                 View view = findViewByPosition(i);
   3160                 if (view == null) {
   3161                     break;
   3162                 }
   3163                 if (view.getVisibility() == View.VISIBLE && view.hasFocusable()) {
   3164                     view.requestFocus();
   3165                     break;
   3166                 }
   3167             }
   3168         }
   3169     }
   3170 
   3171     void setFocusSearchDisabled(boolean disabled) {
   3172         mFlag = (mFlag & ~PF_FOCUS_SEARCH_DISABLED) | (disabled ? PF_FOCUS_SEARCH_DISABLED : 0);
   3173     }
   3174 
   3175     boolean isFocusSearchDisabled() {
   3176         return (mFlag & PF_FOCUS_SEARCH_DISABLED) != 0;
   3177     }
   3178 
   3179     @Override
   3180     public View onInterceptFocusSearch(View focused, int direction) {
   3181         if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
   3182             return focused;
   3183         }
   3184 
   3185         final FocusFinder ff = FocusFinder.getInstance();
   3186         View result = null;
   3187         if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
   3188             // convert direction to absolute direction and see if we have a view there and if not
   3189             // tell LayoutManager to add if it can.
   3190             if (canScrollVertically()) {
   3191                 final int absDir =
   3192                         direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
   3193                 result = ff.findNextFocus(mBaseGridView, focused, absDir);
   3194             }
   3195             if (canScrollHorizontally()) {
   3196                 boolean rtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
   3197                 final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
   3198                         ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
   3199                 result = ff.findNextFocus(mBaseGridView, focused, absDir);
   3200             }
   3201         } else {
   3202             result = ff.findNextFocus(mBaseGridView, focused, direction);
   3203         }
   3204         if (result != null) {
   3205             return result;
   3206         }
   3207 
   3208         if (mBaseGridView.getDescendantFocusability() == ViewGroup.FOCUS_BLOCK_DESCENDANTS) {
   3209             return mBaseGridView.getParent().focusSearch(focused, direction);
   3210         }
   3211 
   3212         if (DEBUG) Log.v(getTag(), "regular focusSearch failed direction " + direction);
   3213         int movement = getMovement(direction);
   3214         final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
   3215         if (movement == NEXT_ITEM) {
   3216             if (isScroll || (mFlag & PF_FOCUS_OUT_END) == 0) {
   3217                 result = focused;
   3218             }
   3219             if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedLastItem()) {
   3220                 processPendingMovement(true);
   3221                 result = focused;
   3222             }
   3223         } else if (movement == PREV_ITEM) {
   3224             if (isScroll || (mFlag & PF_FOCUS_OUT_FRONT) == 0) {
   3225                 result = focused;
   3226             }
   3227             if ((mFlag & PF_SCROLL_ENABLED) != 0 && !hasCreatedFirstItem()) {
   3228                 processPendingMovement(false);
   3229                 result = focused;
   3230             }
   3231         } else if (movement == NEXT_ROW) {
   3232             if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_END) == 0) {
   3233                 result = focused;
   3234             }
   3235         } else if (movement == PREV_ROW) {
   3236             if (isScroll || (mFlag & PF_FOCUS_OUT_SIDE_START) == 0) {
   3237                 result = focused;
   3238             }
   3239         }
   3240         if (result != null) {
   3241             return result;
   3242         }
   3243 
   3244         if (DEBUG) Log.v(getTag(), "now focusSearch in parent");
   3245         result = mBaseGridView.getParent().focusSearch(focused, direction);
   3246         if (result != null) {
   3247             return result;
   3248         }
   3249         return focused != null ? focused : mBaseGridView;
   3250     }
   3251 
   3252     boolean hasPreviousViewInSameRow(int pos) {
   3253         if (mGrid == null || pos == NO_POSITION || mGrid.getFirstVisibleIndex() < 0) {
   3254             return false;
   3255         }
   3256         if (mGrid.getFirstVisibleIndex() > 0) {
   3257             return true;
   3258         }
   3259         final int focusedRow = mGrid.getLocation(pos).row;
   3260         for (int i = getChildCount() - 1; i >= 0; i--) {
   3261             int position = getAdapterPositionByIndex(i);
   3262             Grid.Location loc = mGrid.getLocation(position);
   3263             if (loc != null && loc.row == focusedRow) {
   3264                 if (position < pos) {
   3265                     return true;
   3266                 }
   3267             }
   3268         }
   3269         return false;
   3270     }
   3271 
   3272     @Override
   3273     public boolean onAddFocusables(RecyclerView recyclerView,
   3274             ArrayList<View> views, int direction, int focusableMode) {
   3275         if ((mFlag & PF_FOCUS_SEARCH_DISABLED) != 0) {
   3276             return true;
   3277         }
   3278         // If this viewgroup or one of its children currently has focus then we
   3279         // consider our children for focus searching in main direction on the same row.
   3280         // If this viewgroup has no focus and using focus align, we want the system
   3281         // to ignore our children and pass focus to the viewgroup, which will pass
   3282         // focus on to its children appropriately.
   3283         // If this viewgroup has no focus and not using focus align, we want to
   3284         // consider the child that does not overlap with padding area.
   3285         if (recyclerView.hasFocus()) {
   3286             if (mPendingMoveSmoothScroller != null) {
   3287                 // don't find next focusable if has pending movement.
   3288                 return true;
   3289             }
   3290             final int movement = getMovement(direction);
   3291             final View focused = recyclerView.findFocus();
   3292             final int focusedIndex = findImmediateChildIndex(focused);
   3293             final int focusedPos = getAdapterPositionByIndex(focusedIndex);
   3294             // Even if focusedPos != NO_POSITION, findViewByPosition could return null if the view
   3295             // is ignored or getLayoutPosition does not match the adapter position of focused view.
   3296             final View immediateFocusedChild = (focusedPos == NO_POSITION) ? null
   3297                     : findViewByPosition(focusedPos);
   3298             // Add focusables of focused item.
   3299             if (immediateFocusedChild != null) {
   3300                 immediateFocusedChild.addFocusables(views,  direction, focusableMode);
   3301             }
   3302             if (mGrid == null || getChildCount() == 0) {
   3303                 // no grid information, or no child, bail out.
   3304                 return true;
   3305             }
   3306             if ((movement == NEXT_ROW || movement == PREV_ROW) && mGrid.getNumRows() <= 1) {
   3307                 // For single row, cannot navigate to previous/next row.
   3308                 return true;
   3309             }
   3310             // Add focusables of neighbor depending on the focus search direction.
   3311             final int focusedRow = mGrid != null && immediateFocusedChild != null
   3312                     ? mGrid.getLocation(focusedPos).row : NO_POSITION;
   3313             final int focusableCount = views.size();
   3314             int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
   3315             int loop_end = inc > 0 ? getChildCount() - 1 : 0;
   3316             int loop_start;
   3317             if (focusedIndex == NO_POSITION) {
   3318                 loop_start = inc > 0 ? 0 : getChildCount() - 1;
   3319             } else {
   3320                 loop_start = focusedIndex + inc;
   3321             }
   3322             for (int i = loop_start; inc > 0 ? i <= loop_end : i >= loop_end; i += inc) {
   3323                 final View child = getChildAt(i);
   3324                 if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
   3325                     continue;
   3326                 }
   3327                 // if there wasn't any focused item, add the very first focusable
   3328                 // items and stop.
   3329                 if (immediateFocusedChild == null) {
   3330                     child.addFocusables(views,  direction, focusableMode);
   3331                     if (views.size() > focusableCount) {
   3332                         break;
   3333                     }
   3334                     continue;
   3335                 }
   3336                 int position = getAdapterPositionByIndex(i);
   3337                 Grid.Location loc = mGrid.getLocation(position);
   3338                 if (loc == null) {
   3339                     continue;
   3340                 }
   3341                 if (movement == NEXT_ITEM) {
   3342                     // Add first focusable item on the same row
   3343                     if (loc.row == focusedRow && position > focusedPos) {
   3344                         child.addFocusables(views,  direction, focusableMode);
   3345                         if (views.size() > focusableCount) {
   3346                             break;
   3347                         }
   3348                     }
   3349                 } else if (movement == PREV_ITEM) {
   3350                     // Add first focusable item on the same row
   3351                     if (loc.row == focusedRow && position < focusedPos) {
   3352                         child.addFocusables(views,  direction, focusableMode);
   3353                         if (views.size() > focusableCount) {
   3354                             break;
   3355                         }
   3356                     }
   3357                 } else if (movement == NEXT_ROW) {
   3358                     // Add all focusable items after this item whose row index is bigger
   3359                     if (loc.row == focusedRow) {
   3360                         continue;
   3361                     } else if (loc.row < focusedRow) {
   3362                         break;
   3363                     }
   3364                     child.addFocusables(views,  direction, focusableMode);
   3365                 } else if (movement == PREV_ROW) {
   3366                     // Add all focusable items before this item whose row index is smaller
   3367                     if (loc.row == focusedRow) {
   3368                         continue;
   3369                     } else if (loc.row > focusedRow) {
   3370                         break;
   3371                     }
   3372                     child.addFocusables(views,  direction, focusableMode);
   3373                 }
   3374             }
   3375         } else {
   3376             int focusableCount = views.size();
   3377             if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
   3378                 // adding views not overlapping padding area to avoid scrolling in gaining focus
   3379                 int left = mWindowAlignment.mainAxis().getPaddingMin();
   3380                 int right = mWindowAlignment.mainAxis().getClientSize() + left;
   3381                 for (int i = 0, count = getChildCount(); i < count; i++) {
   3382                     View child = getChildAt(i);
   3383                     if (child.getVisibility() == View.VISIBLE) {
   3384                         if (getViewMin(child) >= left && getViewMax(child) <= right) {
   3385                             child.addFocusables(views, direction, focusableMode);
   3386                         }
   3387                     }
   3388                 }
   3389                 // if we cannot find any, then just add all children.
   3390                 if (views.size() == focusableCount) {
   3391                     for (int i = 0, count = getChildCount(); i < count; i++) {
   3392                         View child = getChildAt(i);
   3393                         if (child.getVisibility() == View.VISIBLE) {
   3394                             child.addFocusables(views, direction, focusableMode);
   3395                         }
   3396                     }
   3397                 }
   3398             } else {
   3399                 View view = findViewByPosition(mFocusPosition);
   3400                 if (view != null) {
   3401                     view.addFocusables(views, direction, focusableMode);
   3402                 }
   3403             }
   3404             // if still cannot find any, fall through and add itself
   3405             if (views.size() != focusableCount) {
   3406                 return true;
   3407             }
   3408             if (recyclerView.isFocusable()) {
   3409                 views.add(recyclerView);
   3410             }
   3411         }
   3412         return true;
   3413     }
   3414 
   3415     boolean hasCreatedLastItem() {
   3416         int count = getItemCount();
   3417         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
   3418     }
   3419 
   3420     boolean hasCreatedFirstItem() {
   3421         int count = getItemCount();
   3422         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
   3423     }
   3424 
   3425     boolean isItemFullyVisible(int pos) {
   3426         RecyclerView.ViewHolder vh = mBaseGridView.findViewHolderForAdapterPosition(pos);
   3427         if (vh == null) {
   3428             return false;
   3429         }
   3430         return vh.itemView.getLeft() >= 0 && vh.itemView.getRight() <= mBaseGridView.getWidth()
   3431                 && vh.itemView.getTop() >= 0 && vh.itemView.getBottom()
   3432                 <= mBaseGridView.getHeight();
   3433     }
   3434 
   3435     boolean canScrollTo(View view) {
   3436         return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
   3437     }
   3438 
   3439     boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
   3440             Rect previouslyFocusedRect) {
   3441         switch (mFocusScrollStrategy) {
   3442             case BaseGridView.FOCUS_SCROLL_ALIGNED:
   3443             default:
   3444                 return gridOnRequestFocusInDescendantsAligned(recyclerView,
   3445                         direction, previouslyFocusedRect);
   3446             case BaseGridView.FOCUS_SCROLL_PAGE:
   3447             case BaseGridView.FOCUS_SCROLL_ITEM:
   3448                 return gridOnRequestFocusInDescendantsUnaligned(recyclerView,
   3449                         direction, previouslyFocusedRect);
   3450         }
   3451     }
   3452 
   3453     private boolean gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView,
   3454             int direction, Rect previouslyFocusedRect) {
   3455         View view = findViewByPosition(mFocusPosition);
   3456         if (view != null) {
   3457             boolean result = view.requestFocus(direction, previouslyFocusedRect);
   3458             if (!result && DEBUG) {
   3459                 Log.w(getTag(), "failed to request focus on " + view);
   3460             }
   3461             return result;
   3462         }
   3463         return false;
   3464     }
   3465 
   3466     private boolean gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView,
   3467             int direction, Rect previouslyFocusedRect) {
   3468         // focus to view not overlapping padding area to avoid scrolling in gaining focus
   3469         int index;
   3470         int increment;
   3471         int end;
   3472         int count = getChildCount();
   3473         if ((direction & View.FOCUS_FORWARD) != 0) {
   3474             index = 0;
   3475             increment = 1;
   3476             end = count;
   3477         } else {
   3478             index = count - 1;
   3479             increment = -1;
   3480             end = -1;
   3481         }
   3482         int left = mWindowAlignment.mainAxis().getPaddingMin();
   3483         int right = mWindowAlignment.mainAxis().getClientSize() + left;
   3484         for (int i = index; i != end; i += increment) {
   3485             View child = getChildAt(i);
   3486             if (child.getVisibility() == View.VISIBLE) {
   3487                 if (getViewMin(child) >= left && getViewMax(child) <= right) {
   3488                     if (child.requestFocus(direction, previouslyFocusedRect)) {
   3489                         return true;
   3490                     }
   3491                 }
   3492             }
   3493         }
   3494         return false;
   3495     }
   3496 
   3497     private final static int PREV_ITEM = 0;
   3498     private final static int NEXT_ITEM = 1;
   3499     private final static int PREV_ROW = 2;
   3500     private final static int NEXT_ROW = 3;
   3501 
   3502     private int getMovement(int direction) {
   3503         int movement = View.FOCUS_LEFT;
   3504 
   3505         if (mOrientation == HORIZONTAL) {
   3506             switch(direction) {
   3507                 case View.FOCUS_LEFT:
   3508                     movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? PREV_ITEM : NEXT_ITEM;
   3509                     break;
   3510                 case View.FOCUS_RIGHT:
   3511                     movement = (mFlag & PF_REVERSE_FLOW_PRIMARY) == 0 ? NEXT_ITEM : PREV_ITEM;
   3512                     break;
   3513                 case View.FOCUS_UP:
   3514                     movement = PREV_ROW;
   3515                     break;
   3516                 case View.FOCUS_DOWN:
   3517                     movement = NEXT_ROW;
   3518                     break;
   3519             }
   3520         } else if (mOrientation == VERTICAL) {
   3521             switch(direction) {
   3522                 case View.FOCUS_LEFT:
   3523                     movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? PREV_ROW : NEXT_ROW;
   3524                     break;
   3525                 case View.FOCUS_RIGHT:
   3526                     movement = (mFlag & PF_REVERSE_FLOW_SECONDARY) == 0 ? NEXT_ROW : PREV_ROW;
   3527                     break;
   3528                 case View.FOCUS_UP:
   3529                     movement = PREV_ITEM;
   3530                     break;
   3531                 case View.FOCUS_DOWN:
   3532                     movement = NEXT_ITEM;
   3533                     break;
   3534             }
   3535         }
   3536 
   3537         return movement;
   3538     }
   3539 
   3540     int getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i) {
   3541         View view = findViewByPosition(mFocusPosition);
   3542         if (view == null) {
   3543             return i;
   3544         }
   3545         int focusIndex = recyclerView.indexOfChild(view);
   3546         // supposely 0 1 2 3 4 5 6 7 8 9, 4 is the center item
   3547         // drawing order is 0 1 2 3 9 8 7 6 5 4
   3548         if (i < focusIndex) {
   3549             return i;
   3550         } else if (i < childCount - 1) {
   3551             return focusIndex + childCount - 1 - i;
   3552         } else {
   3553             return focusIndex;
   3554         }
   3555     }
   3556 
   3557     @Override
   3558     public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
   3559             RecyclerView.Adapter newAdapter) {
   3560         if (DEBUG) Log.v(getTag(), "onAdapterChanged to " + newAdapter);
   3561         if (oldAdapter != null) {
   3562             discardLayoutInfo();
   3563             mFocusPosition = NO_POSITION;
   3564             mFocusPositionOffset = 0;
   3565             mChildrenStates.clear();
   3566         }
   3567         if (newAdapter instanceof FacetProviderAdapter) {
   3568             mFacetProviderAdapter = (FacetProviderAdapter) newAdapter;
   3569         } else {
   3570             mFacetProviderAdapter = null;
   3571         }
   3572         super.onAdapterChanged(oldAdapter, newAdapter);
   3573     }
   3574 
   3575     private void discardLayoutInfo() {
   3576         mGrid = null;
   3577         mRowSizeSecondary = null;
   3578         mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
   3579     }
   3580 
   3581     public void setLayoutEnabled(boolean layoutEnabled) {
   3582         if (((mFlag & PF_LAYOUT_ENABLED) != 0) != layoutEnabled) {
   3583             mFlag = (mFlag & ~PF_LAYOUT_ENABLED) | (layoutEnabled ? PF_LAYOUT_ENABLED : 0);
   3584             requestLayout();
   3585         }
   3586     }
   3587 
   3588     void setChildrenVisibility(int visibility) {
   3589         mChildVisibility = visibility;
   3590         if (mChildVisibility != -1) {
   3591             int count = getChildCount();
   3592             for (int i= 0; i < count; i++) {
   3593                 getChildAt(i).setVisibility(mChildVisibility);
   3594             }
   3595         }
   3596     }
   3597 
   3598     final static class SavedState implements Parcelable {
   3599 
   3600         int index; // index inside adapter of the current view
   3601         Bundle childStates = Bundle.EMPTY;
   3602 
   3603         @Override
   3604         public void writeToParcel(Parcel out, int flags) {
   3605             out.writeInt(index);
   3606             out.writeBundle(childStates);
   3607         }
   3608 
   3609         @SuppressWarnings("hiding")
   3610         public static final Parcelable.Creator<SavedState> CREATOR =
   3611                 new Parcelable.Creator<SavedState>() {
   3612                     @Override
   3613                     public SavedState createFromParcel(Parcel in) {
   3614                         return new SavedState(in);
   3615                     }
   3616 
   3617                     @Override
   3618                     public SavedState[] newArray(int size) {
   3619                         return new SavedState[size];
   3620                     }
   3621                 };
   3622 
   3623         @Override
   3624         public int describeContents() {
   3625             return 0;
   3626         }
   3627 
   3628         SavedState(Parcel in) {
   3629             index = in.readInt();
   3630             childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
   3631         }
   3632 
   3633         SavedState() {
   3634         }
   3635     }
   3636 
   3637     @Override
   3638     public Parcelable onSaveInstanceState() {
   3639         if (DEBUG) Log.v(getTag(), "onSaveInstanceState getSelection() " + getSelection());
   3640         SavedState ss = new SavedState();
   3641         // save selected index
   3642         ss.index = getSelection();
   3643         // save offscreen child (state when they are recycled)
   3644         Bundle bundle = mChildrenStates.saveAsBundle();
   3645         // save views currently is on screen (TODO save cached views)
   3646         for (int i = 0, count = getChildCount(); i < count; i++) {
   3647             View view = getChildAt(i);
   3648             int position = getAdapterPositionByView(view);
   3649             if (position != NO_POSITION) {
   3650                 bundle = mChildrenStates.saveOnScreenView(bundle, view, position);
   3651             }
   3652         }
   3653         ss.childStates = bundle;
   3654         return ss;
   3655     }
   3656 
   3657     void onChildRecycled(RecyclerView.ViewHolder holder) {
   3658         final int position = holder.getAdapterPosition();
   3659         if (position != NO_POSITION) {
   3660             mChildrenStates.saveOffscreenView(holder.itemView, position);
   3661         }
   3662     }
   3663 
   3664     @Override
   3665     public void onRestoreInstanceState(Parcelable state) {
   3666         if (!(state instanceof SavedState)) {
   3667             return;
   3668         }
   3669         SavedState loadingState = (SavedState)state;
   3670         mFocusPosition = loadingState.index;
   3671         mFocusPositionOffset = 0;
   3672         mChildrenStates.loadFromBundle(loadingState.childStates);
   3673         mFlag |= PF_FORCE_FULL_LAYOUT;
   3674         requestLayout();
   3675         if (DEBUG) Log.v(getTag(), "onRestoreInstanceState mFocusPosition " + mFocusPosition);
   3676     }
   3677 
   3678     @Override
   3679     public int getRowCountForAccessibility(RecyclerView.Recycler recycler,
   3680             RecyclerView.State state) {
   3681         if (mOrientation == HORIZONTAL && mGrid != null) {
   3682             return mGrid.getNumRows();
   3683         }
   3684         return super.getRowCountForAccessibility(recycler, state);
   3685     }
   3686 
   3687     @Override
   3688     public int getColumnCountForAccessibility(RecyclerView.Recycler recycler,
   3689             RecyclerView.State state) {
   3690         if (mOrientation == VERTICAL && mGrid != null) {
   3691             return mGrid.getNumRows();
   3692         }
   3693         return super.getColumnCountForAccessibility(recycler, state);
   3694     }
   3695 
   3696     @Override
   3697     public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler,
   3698             RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) {
   3699         ViewGroup.LayoutParams lp = host.getLayoutParams();
   3700         if (mGrid == null || !(lp instanceof LayoutParams)) {
   3701             return;
   3702         }
   3703         LayoutParams glp = (LayoutParams) lp;
   3704         int position = glp.getViewAdapterPosition();
   3705         int rowIndex = position >= 0 ? mGrid.getRowIndex(position) : -1;
   3706         if (rowIndex < 0) {
   3707             return;
   3708         }
   3709         int guessSpanIndex = position / mGrid.getNumRows();
   3710         if (mOrientation == HORIZONTAL) {
   3711             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
   3712                     rowIndex, 1, guessSpanIndex, 1, false, false));
   3713         } else {
   3714             info.setCollectionItemInfo(AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(
   3715                     guessSpanIndex, 1, rowIndex, 1, false, false));
   3716         }
   3717     }
   3718 
   3719     /*
   3720      * Leanback widget is different than the default implementation because the "scroll" is driven
   3721      * by selection change.
   3722      */
   3723     @Override
   3724     public boolean performAccessibilityAction(Recycler recycler, State state, int action,
   3725             Bundle args) {
   3726         if (!isScrollEnabled()) {
   3727             // eat action request so that talkback wont focus out of RV
   3728             return true;
   3729         }
   3730         saveContext(recycler, state);
   3731         int translatedAction = action;
   3732         boolean reverseFlowPrimary = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0;
   3733         if (Build.VERSION.SDK_INT >= 23) {
   3734             if (mOrientation == HORIZONTAL) {
   3735                 if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3736                         .ACTION_SCROLL_LEFT.getId()) {
   3737                     translatedAction = reverseFlowPrimary
   3738                             ? AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD :
   3739                             AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
   3740                 } else if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3741                         .ACTION_SCROLL_RIGHT.getId()) {
   3742                     translatedAction = reverseFlowPrimary
   3743                             ? AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD :
   3744                             AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
   3745                 }
   3746             } else { // VERTICAL layout
   3747                 if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP
   3748                         .getId()) {
   3749                     translatedAction = AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD;
   3750                 } else if (action == AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3751                         .ACTION_SCROLL_DOWN.getId()) {
   3752                     translatedAction = AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD;
   3753                 }
   3754             }
   3755         }
   3756         switch (translatedAction) {
   3757             case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:
   3758                 processPendingMovement(false);
   3759                 processSelectionMoves(false, -1);
   3760                 break;
   3761             case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:
   3762                 processPendingMovement(true);
   3763                 processSelectionMoves(false, 1);
   3764                 break;
   3765         }
   3766         leaveContext();
   3767         return true;
   3768     }
   3769 
   3770     /*
   3771      * Move mFocusPosition multiple steps on the same row in main direction.
   3772      * Stops when moves are all consumed or reach first/last visible item.
   3773      * Returning remaining moves.
   3774      */
   3775     int processSelectionMoves(boolean preventScroll, int moves) {
   3776         if (mGrid == null) {
   3777             return moves;
   3778         }
   3779         int focusPosition = mFocusPosition;
   3780         int focusedRow = focusPosition != NO_POSITION
   3781                 ? mGrid.getRowIndex(focusPosition) : NO_POSITION;
   3782         View newSelected = null;
   3783         for (int i = 0, count = getChildCount(); i < count && moves != 0; i++) {
   3784             int index = moves > 0 ? i : count - 1 - i;
   3785             final View child = getChildAt(index);
   3786             if (!canScrollTo(child)) {
   3787                 continue;
   3788             }
   3789             int position = getAdapterPositionByIndex(index);
   3790             int rowIndex = mGrid.getRowIndex(position);
   3791             if (focusedRow == NO_POSITION) {
   3792                 focusPosition = position;
   3793                 newSelected = child;
   3794                 focusedRow = rowIndex;
   3795             } else if (rowIndex == focusedRow) {
   3796                 if ((moves > 0 && position > focusPosition)
   3797                         || (moves < 0 && position < focusPosition)) {
   3798                     focusPosition = position;
   3799                     newSelected = child;
   3800                     if (moves > 0) {
   3801                         moves--;
   3802                     } else {
   3803                         moves++;
   3804                     }
   3805                 }
   3806             }
   3807         }
   3808         if (newSelected != null) {
   3809             if (preventScroll) {
   3810                 if (hasFocus()) {
   3811                     mFlag |= PF_IN_SELECTION;
   3812                     newSelected.requestFocus();
   3813                     mFlag &= ~PF_IN_SELECTION;
   3814                 }
   3815                 mFocusPosition = focusPosition;
   3816                 mSubFocusPosition = 0;
   3817             } else {
   3818                 scrollToView(newSelected, true);
   3819             }
   3820         }
   3821         return moves;
   3822     }
   3823 
   3824     @Override
   3825     public void onInitializeAccessibilityNodeInfo(Recycler recycler, State state,
   3826             AccessibilityNodeInfoCompat info) {
   3827         saveContext(recycler, state);
   3828         int count = state.getItemCount();
   3829         boolean reverseFlowPrimary = (mFlag & PF_REVERSE_FLOW_PRIMARY) != 0;
   3830         if (count > 1 && !isItemFullyVisible(0)) {
   3831             if (Build.VERSION.SDK_INT >= 23) {
   3832                 if (mOrientation == HORIZONTAL) {
   3833                     info.addAction(reverseFlowPrimary
   3834                             ? AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3835                                     .ACTION_SCROLL_RIGHT :
   3836                             AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3837                                     .ACTION_SCROLL_LEFT);
   3838                 } else {
   3839                     info.addAction(
   3840                             AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_SCROLL_UP);
   3841                 }
   3842             } else {
   3843                 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD);
   3844             }
   3845             info.setScrollable(true);
   3846         }
   3847         if (count > 1 && !isItemFullyVisible(count - 1)) {
   3848             if (Build.VERSION.SDK_INT >= 23) {
   3849                 if (mOrientation == HORIZONTAL) {
   3850                     info.addAction(reverseFlowPrimary
   3851                             ? AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3852                                     .ACTION_SCROLL_LEFT :
   3853                             AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3854                                     .ACTION_SCROLL_RIGHT);
   3855                 } else {
   3856                     info.addAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat
   3857                                     .ACTION_SCROLL_DOWN);
   3858                 }
   3859             } else {
   3860                 info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD);
   3861             }
   3862             info.setScrollable(true);
   3863         }
   3864         final AccessibilityNodeInfoCompat.CollectionInfoCompat collectionInfo =
   3865                 AccessibilityNodeInfoCompat.CollectionInfoCompat
   3866                         .obtain(getRowCountForAccessibility(recycler, state),
   3867                                 getColumnCountForAccessibility(recycler, state),
   3868                                 isLayoutHierarchical(recycler, state),
   3869                                 getSelectionModeForAccessibility(recycler, state));
   3870         info.setCollectionInfo(collectionInfo);
   3871         leaveContext();
   3872     }
   3873 }
   3874