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 
LayoutParams(Context c, AttributeSet attrs)79         public LayoutParams(Context c, AttributeSet attrs) {
80             super(c, attrs);
81         }
82 
LayoutParams(int width, int height)83         public LayoutParams(int width, int height) {
84             super(width, height);
85         }
86 
LayoutParams(MarginLayoutParams source)87         public LayoutParams(MarginLayoutParams source) {
88             super(source);
89         }
90 
LayoutParams(ViewGroup.LayoutParams source)91         public LayoutParams(ViewGroup.LayoutParams source) {
92             super(source);
93         }
94 
LayoutParams(RecyclerView.LayoutParams source)95         public LayoutParams(RecyclerView.LayoutParams source) {
96             super(source);
97         }
98 
LayoutParams(LayoutParams source)99         public LayoutParams(LayoutParams source) {
100             super(source);
101         }
102 
getAlignX()103         int getAlignX() {
104             return mAlignX;
105         }
106 
getAlignY()107         int getAlignY() {
108             return mAlignY;
109         }
110 
getOpticalLeft(View view)111         int getOpticalLeft(View view) {
112             return view.getLeft() + mLeftInset;
113         }
114 
getOpticalTop(View view)115         int getOpticalTop(View view) {
116             return view.getTop() + mTopInset;
117         }
118 
getOpticalRight(View view)119         int getOpticalRight(View view) {
120             return view.getRight() - mRightInset;
121         }
122 
getOpticalBottom(View view)123         int getOpticalBottom(View view) {
124             return view.getBottom() - mBottomInset;
125         }
126 
getOpticalWidth(View view)127         int getOpticalWidth(View view) {
128             return view.getWidth() - mLeftInset - mRightInset;
129         }
130 
getOpticalHeight(View view)131         int getOpticalHeight(View view) {
132             return view.getHeight() - mTopInset - mBottomInset;
133         }
134 
getOpticalLeftInset()135         int getOpticalLeftInset() {
136             return mLeftInset;
137         }
138 
getOpticalRightInset()139         int getOpticalRightInset() {
140             return mRightInset;
141         }
142 
getOpticalTopInset()143         int getOpticalTopInset() {
144             return mTopInset;
145         }
146 
getOpticalBottomInset()147         int getOpticalBottomInset() {
148             return mBottomInset;
149         }
150 
setAlignX(int alignX)151         void setAlignX(int alignX) {
152             mAlignX = alignX;
153         }
154 
setAlignY(int alignY)155         void setAlignY(int alignY) {
156             mAlignY = alignY;
157         }
158 
setItemAlignmentFacet(ItemAlignmentFacet facet)159         void setItemAlignmentFacet(ItemAlignmentFacet facet) {
160             mAlignmentFacet = facet;
161         }
162 
getItemAlignmentFacet()163         ItemAlignmentFacet getItemAlignmentFacet() {
164             return mAlignmentFacet;
165         }
166 
calculateItemAlignments(int orientation, View view)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 
getAlignMultiple()183         int[] getAlignMultiple() {
184             return mAlignMultiple;
185         }
186 
setOpticalInsets(int leftInset, int topInset, int rightInset, int bottomInset)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 
GridLinearSmoothScroller()202         GridLinearSmoothScroller() {
203             super(mBaseGridView.getContext());
204         }
205 
206         @Override
onStop()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 
onStopInternal()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
calculateTimeForScrolling(int dx)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
onTargetFound(View targetView, RecyclerView.State state, Action action)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 
PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid)289         PendingMoveSmoothScroller(int initialPendingMoves, boolean staggeredGrid) {
290             mPendingMoves = initialPendingMoves;
291             mStaggeredGrid = staggeredGrid;
292             setTargetPosition(TARGET_UNDEFINED);
293         }
294 
increasePendingMoves()295         void increasePendingMoves() {
296             if (mPendingMoves < mMaxPendingMoves) {
297                 mPendingMoves++;
298             }
299         }
300 
decreasePendingMoves()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          */
consumePendingMovesBeforeLayout()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          */
consumePendingMovesAfterLayout()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
updateActionForInterimTarget(Action action)361         protected void updateActionForInterimTarget(Action action) {
362             if (mPendingMoves == 0) {
363                 return;
364             }
365             super.updateActionForInterimTarget(action);
366         }
367 
368         @Override
computeScrollVectorForPosition(int targetPosition)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
onStopInternal()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 
getTag()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 
GridLayoutManager(BaseGridView baseGridView)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 
setOrientation(@ecyclerView.Orientation int orientation)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 
onRtlPropertiesChanged(int layoutDirection)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 
getFocusScrollStrategy()746     public int getFocusScrollStrategy() {
747         return mFocusScrollStrategy;
748     }
749 
setFocusScrollStrategy(int focusScrollStrategy)750     public void setFocusScrollStrategy(int focusScrollStrategy) {
751         mFocusScrollStrategy = focusScrollStrategy;
752     }
753 
setWindowAlignment(int windowAlignment)754     public void setWindowAlignment(int windowAlignment) {
755         mWindowAlignment.mainAxis().setWindowAlignment(windowAlignment);
756     }
757 
getWindowAlignment()758     public int getWindowAlignment() {
759         return mWindowAlignment.mainAxis().getWindowAlignment();
760     }
761 
setWindowAlignmentOffset(int alignmentOffset)762     public void setWindowAlignmentOffset(int alignmentOffset) {
763         mWindowAlignment.mainAxis().setWindowAlignmentOffset(alignmentOffset);
764     }
765 
getWindowAlignmentOffset()766     public int getWindowAlignmentOffset() {
767         return mWindowAlignment.mainAxis().getWindowAlignmentOffset();
768     }
769 
setWindowAlignmentOffsetPercent(float offsetPercent)770     public void setWindowAlignmentOffsetPercent(float offsetPercent) {
771         mWindowAlignment.mainAxis().setWindowAlignmentOffsetPercent(offsetPercent);
772     }
773 
getWindowAlignmentOffsetPercent()774     public float getWindowAlignmentOffsetPercent() {
775         return mWindowAlignment.mainAxis().getWindowAlignmentOffsetPercent();
776     }
777 
setItemAlignmentOffset(int alignmentOffset)778     public void setItemAlignmentOffset(int alignmentOffset) {
779         mItemAlignment.mainAxis().setItemAlignmentOffset(alignmentOffset);
780         updateChildAlignments();
781     }
782 
getItemAlignmentOffset()783     public int getItemAlignmentOffset() {
784         return mItemAlignment.mainAxis().getItemAlignmentOffset();
785     }
786 
setItemAlignmentOffsetWithPadding(boolean withPadding)787     public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
788         mItemAlignment.mainAxis().setItemAlignmentOffsetWithPadding(withPadding);
789         updateChildAlignments();
790     }
791 
isItemAlignmentOffsetWithPadding()792     public boolean isItemAlignmentOffsetWithPadding() {
793         return mItemAlignment.mainAxis().isItemAlignmentOffsetWithPadding();
794     }
795 
setItemAlignmentOffsetPercent(float offsetPercent)796     public void setItemAlignmentOffsetPercent(float offsetPercent) {
797         mItemAlignment.mainAxis().setItemAlignmentOffsetPercent(offsetPercent);
798         updateChildAlignments();
799     }
800 
getItemAlignmentOffsetPercent()801     public float getItemAlignmentOffsetPercent() {
802         return mItemAlignment.mainAxis().getItemAlignmentOffsetPercent();
803     }
804 
setItemAlignmentViewId(int viewId)805     public void setItemAlignmentViewId(int viewId) {
806         mItemAlignment.mainAxis().setItemAlignmentViewId(viewId);
807         updateChildAlignments();
808     }
809 
getItemAlignmentViewId()810     public int getItemAlignmentViewId() {
811         return mItemAlignment.mainAxis().getItemAlignmentViewId();
812     }
813 
setFocusOutAllowed(boolean throughFront, boolean throughEnd)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 
setFocusOutSideAllowed(boolean throughStart, boolean throughEnd)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 
setNumRows(int numRows)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      */
setRowHeight(int height)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 
setItemSpacing(int space)842     public void setItemSpacing(int space) {
843         mVerticalSpacing = mHorizontalSpacing = space;
844         mSpacingPrimary = mSpacingSecondary = space;
845     }
846 
setVerticalSpacing(int space)847     public void setVerticalSpacing(int space) {
848         if (mOrientation == VERTICAL) {
849             mSpacingPrimary = mVerticalSpacing = space;
850         } else {
851             mSpacingSecondary = mVerticalSpacing = space;
852         }
853     }
854 
setHorizontalSpacing(int space)855     public void setHorizontalSpacing(int space) {
856         if (mOrientation == HORIZONTAL) {
857             mSpacingPrimary = mHorizontalSpacing = space;
858         } else {
859             mSpacingSecondary = mHorizontalSpacing = space;
860         }
861     }
862 
getVerticalSpacing()863     public int getVerticalSpacing() {
864         return mVerticalSpacing;
865     }
866 
getHorizontalSpacing()867     public int getHorizontalSpacing() {
868         return mHorizontalSpacing;
869     }
870 
setGravity(int gravity)871     public void setGravity(int gravity) {
872         mGravity = gravity;
873     }
874 
hasDoneFirstLayout()875     protected boolean hasDoneFirstLayout() {
876         return mGrid != null;
877     }
878 
setOnChildSelectedListener(OnChildSelectedListener listener)879     public void setOnChildSelectedListener(OnChildSelectedListener listener) {
880         mChildSelectedListener = listener;
881     }
882 
setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)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 
addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)896     public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
897         if (mChildViewHolderSelectedListeners == null) {
898             mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
899         }
900         mChildViewHolderSelectedListeners.add(listener);
901     }
902 
removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)903     public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
904             listener) {
905         if (mChildViewHolderSelectedListeners != null) {
906             mChildViewHolderSelectedListeners.remove(listener);
907         }
908     }
909 
hasOnChildViewHolderSelectedListener()910     boolean hasOnChildViewHolderSelectedListener() {
911         return mChildViewHolderSelectedListeners != null
912                 && mChildViewHolderSelectedListeners.size() > 0;
913     }
914 
fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition)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 
fireOnChildViewHolderSelectedAndPositioned(RecyclerView parent, RecyclerView.ViewHolder child, int position, int subposition)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 
setOnChildLaidOutListener(OnChildLaidOutListener listener)937     void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
938         mChildLaidOutListener = listener;
939     }
940 
getAdapterPositionByView(View view)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 
getSubPositionByView(View view, View childView)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 
getAdapterPositionByIndex(int index)978     private int getAdapterPositionByIndex(int index) {
979         return getAdapterPositionByView(getChildAt(index));
980     }
981 
dispatchChildSelected()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 
dispatchChildSelectedAndPositioned()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
canScrollHorizontally()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
canScrollVertically()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
generateDefaultLayoutParams()1060     public RecyclerView.LayoutParams generateDefaultLayoutParams() {
1061         return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
1062                 ViewGroup.LayoutParams.WRAP_CONTENT);
1063     }
1064 
1065     @Override
generateLayoutParams(Context context, AttributeSet attrs)1066     public RecyclerView.LayoutParams generateLayoutParams(Context context, AttributeSet attrs) {
1067         return new LayoutParams(context, attrs);
1068     }
1069 
1070     @Override
generateLayoutParams(ViewGroup.LayoutParams lp)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 
getViewForPosition(int position)1083     protected View getViewForPosition(int position) {
1084         return mRecycler.getViewForPosition(position);
1085     }
1086 
getOpticalLeft(View v)1087     final int getOpticalLeft(View v) {
1088         return ((LayoutParams) v.getLayoutParams()).getOpticalLeft(v);
1089     }
1090 
getOpticalRight(View v)1091     final int getOpticalRight(View v) {
1092         return ((LayoutParams) v.getLayoutParams()).getOpticalRight(v);
1093     }
1094 
getOpticalTop(View v)1095     final int getOpticalTop(View v) {
1096         return ((LayoutParams) v.getLayoutParams()).getOpticalTop(v);
1097     }
1098 
getOpticalBottom(View v)1099     final int getOpticalBottom(View v) {
1100         return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
1101     }
1102 
1103     @Override
getDecoratedLeft(View child)1104     public int getDecoratedLeft(View child) {
1105         return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
1106     }
1107 
1108     @Override
getDecoratedTop(View child)1109     public int getDecoratedTop(View child) {
1110         return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
1111     }
1112 
1113     @Override
getDecoratedRight(View child)1114     public int getDecoratedRight(View child) {
1115         return super.getDecoratedRight(child)
1116                 - ((LayoutParams) child.getLayoutParams()).mRightInset;
1117     }
1118 
1119     @Override
getDecoratedBottom(View child)1120     public int getDecoratedBottom(View child) {
1121         return super.getDecoratedBottom(child)
1122                 - ((LayoutParams) child.getLayoutParams()).mBottomInset;
1123     }
1124 
1125     @Override
getDecoratedBoundsWithMargins(View view, Rect outBounds)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 
getViewMin(View v)1135     int getViewMin(View v) {
1136         return mOrientationHelper.getDecoratedStart(v);
1137     }
1138 
getViewMax(View v)1139     int getViewMax(View v) {
1140         return mOrientationHelper.getDecoratedEnd(v);
1141     }
1142 
getViewPrimarySize(View view)1143     int getViewPrimarySize(View view) {
1144         getDecoratedBoundsWithMargins(view, sTempRect);
1145         return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
1146     }
1147 
getViewCenter(View view)1148     private int getViewCenter(View view) {
1149         return (mOrientation == HORIZONTAL) ? getViewCenterX(view) : getViewCenterY(view);
1150     }
1151 
getAdjustedViewCenter(View view)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 
getViewCenterSecondary(View view)1162     private int getViewCenterSecondary(View view) {
1163         return (mOrientation == HORIZONTAL) ? getViewCenterY(view) : getViewCenterX(view);
1164     }
1165 
getViewCenterX(View v)1166     private int getViewCenterX(View v) {
1167         LayoutParams p = (LayoutParams) v.getLayoutParams();
1168         return p.getOpticalLeft(v) + p.getAlignX();
1169     }
1170 
getViewCenterY(View v)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      */
saveContext(Recycler recycler, State state)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      */
leaveContext()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      */
layoutInit()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 
getRowSizeSecondary(int rowIndex)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 
getRowStartSecondary(int rowIndex)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 
getSizeSecondary()1270     private int getSizeSecondary() {
1271         int rightmostIndex = (mFlag & PF_REVERSE_FLOW_SECONDARY) != 0 ? 0 : mNumRows - 1;
1272         return getRowStartSecondary(rightmostIndex) + getRowSizeSecondary(rightmostIndex);
1273     }
1274 
getDecoratedMeasuredWidthWithMargin(View v)1275     int getDecoratedMeasuredWidthWithMargin(View v) {
1276         final LayoutParams lp = (LayoutParams) v.getLayoutParams();
1277         return getDecoratedMeasuredWidth(v) + lp.leftMargin + lp.rightMargin;
1278     }
1279 
getDecoratedMeasuredHeightWithMargin(View v)1280     int getDecoratedMeasuredHeightWithMargin(View v) {
1281         final LayoutParams lp = (LayoutParams) v.getLayoutParams();
1282         return getDecoratedMeasuredHeight(v) + lp.topMargin + lp.bottomMargin;
1283     }
1284 
measureScrapChild(int position, int widthSpec, int heightSpec, int[] measuredDimension)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 
processRowSizeSecondary(boolean measure)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      */
updateRowSecondarySizeRefresh()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 
forceRequestLayout()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
onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec)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 
measureChild(View child)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      */
getFacet(RecyclerView.ViewHolder vh, Class<? extends E> facetClass)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 
layoutChild(int rowIndex, View v, int start, int end, int startSecondary)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 
updateChildAlignments(View v)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 
updateChildAlignments()1803     private void updateChildAlignments() {
1804         for (int i = 0, c = getChildCount(); i < c; i++) {
1805             updateChildAlignments(getChildAt(i));
1806         }
1807     }
1808 
setExtraLayoutSpace(int extraLayoutSpace)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 
getExtraLayoutSpace()1819     int getExtraLayoutSpace() {
1820         return mExtraLayoutSpace;
1821     }
1822 
removeInvisibleViewsAtEnd()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 
removeInvisibleViewsAtFront()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 
appendOneColumnVisibleItems()1837     private boolean appendOneColumnVisibleItems() {
1838         return mGrid.appendOneColumnVisibleItems();
1839     }
1840 
slideIn()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 
getSlideOutDistance()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 
isSlidingChildViews()1904     boolean isSlidingChildViews() {
1905         return (mFlag & PF_SLIDING) != 0;
1906     }
1907 
1908     /**
1909      * Temporarily slide out child and block layout and scroll requests.
1910      */
slideOut()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 
prependOneColumnVisibleItems()1928     private boolean prependOneColumnVisibleItems() {
1929         return mGrid.prependOneColumnVisibleItems();
1930     }
1931 
appendVisibleItems()1932     private void appendVisibleItems() {
1933         mGrid.appendVisibleItems((mFlag & PF_REVERSE_FLOW_PRIMARY) != 0
1934                 ? -mExtraLayoutSpace - mExtraLayoutSpaceInPreLayout
1935                 : mSizePrimary + mExtraLayoutSpace + mExtraLayoutSpaceInPreLayout);
1936     }
1937 
prependVisibleItems()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      */
fastRelayout()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
removeAndRecycleAllViews(RecyclerView.Recycler recycler)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.
focusToViewInLayout(boolean hadFocus, boolean alignToView, int extraDelta, int extraDeltaSecondary)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 {
onLayoutCompleted(RecyclerView.State state)2070         public void onLayoutCompleted(RecyclerView.State state) {
2071         }
2072     }
2073 
2074     @VisibleForTesting
2075     OnLayoutCompleteListener mLayoutCompleteListener;
2076 
2077     @Override
onLayoutCompleted(State state)2078     public void onLayoutCompleted(State state) {
2079         if (mLayoutCompleteListener != null) {
2080             mLayoutCompleteListener.onLayoutCompleted(state);
2081         }
2082     }
2083 
2084     @Override
supportsPredictiveItemAnimations()2085     public boolean supportsPredictiveItemAnimations() {
2086         return true;
2087     }
2088 
updatePositionToRowMapInPostLayout()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 
fillScrapViewsInPostLayout()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.
updatePositionDeltaInPreLayout()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
onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state)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 
offsetChildrenSecondary(int increment)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 
offsetChildrenPrimary(int increment)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
scrollHorizontallyBy(int dx, Recycler recycler, RecyclerView.State state)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
scrollVerticallyBy(int dy, Recycler recycler, RecyclerView.State state)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
scrollDirectionPrimary(int da)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 
hasCreatedLastItem()3415     boolean hasCreatedLastItem() {
3416         int count = getItemCount();
3417         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
3418     }
3419 
hasCreatedFirstItem()3420     boolean hasCreatedFirstItem() {
3421         int count = getItemCount();
3422         return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
3423     }
3424 
isItemFullyVisible(int pos)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 
canScrollTo(View view)3435     boolean canScrollTo(View view) {
3436         return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
3437     }
3438 
gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction, Rect previouslyFocusedRect)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 
gridOnRequestFocusInDescendantsAligned(RecyclerView recyclerView, int direction, Rect previouslyFocusedRect)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 
gridOnRequestFocusInDescendantsUnaligned(RecyclerView recyclerView, int direction, Rect previouslyFocusedRect)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 
getMovement(int direction)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 
getChildDrawingOrder(RecyclerView recyclerView, int childCount, int i)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
onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter)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 
discardLayoutInfo()3575     private void discardLayoutInfo() {
3576         mGrid = null;
3577         mRowSizeSecondary = null;
3578         mFlag &= ~PF_ROW_SECONDARY_SIZE_REFRESH;
3579     }
3580 
setLayoutEnabled(boolean layoutEnabled)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 
setChildrenVisibility(int visibility)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
writeToParcel(Parcel out, int flags)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
describeContents()3624         public int describeContents() {
3625             return 0;
3626         }
3627 
SavedState(Parcel in)3628         SavedState(Parcel in) {
3629             index = in.readInt();
3630             childStates = in.readBundle(GridLayoutManager.class.getClassLoader());
3631         }
3632 
SavedState()3633         SavedState() {
3634         }
3635     }
3636 
3637     @Override
onSaveInstanceState()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 
onChildRecycled(RecyclerView.ViewHolder holder)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
onRestoreInstanceState(Parcelable state)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
getRowCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)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
getColumnCountForAccessibility(RecyclerView.Recycler recycler, RecyclerView.State state)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
onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)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
performAccessibilityAction(Recycler recycler, State state, int action, Bundle args)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      */
processSelectionMoves(boolean preventScroll, int moves)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
onInitializeAccessibilityNodeInfo(Recycler recycler, State state, AccessibilityNodeInfoCompat info)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