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