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.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
19 
20 import android.annotation.SuppressLint;
21 import android.content.Context;
22 import android.content.res.TypedArray;
23 import android.graphics.Rect;
24 import android.util.AttributeSet;
25 import android.view.Gravity;
26 import android.view.KeyEvent;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.animation.Interpolator;
30 
31 import androidx.annotation.RestrictTo;
32 import androidx.recyclerview.widget.RecyclerView;
33 import androidx.recyclerview.widget.SimpleItemAnimator;
34 
35 import org.jspecify.annotations.NonNull;
36 import org.jspecify.annotations.Nullable;
37 
38 /**
39  * An abstract base class for vertically and horizontally scrolling lists. The items come
40  * from the {@link RecyclerView.Adapter} associated with this view.
41  * Do not directly use this class, use {@link VerticalGridView} and {@link HorizontalGridView}.
42  * The class is not intended to be subclassed other than {@link VerticalGridView} and
43  * {@link HorizontalGridView}.
44  */
45 public abstract class BaseGridView extends RecyclerView {
46 
47     /**
48      * Always keep focused item at a aligned position.  Developer can use
49      * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
50      * In this mode, the last focused position will be remembered and restored when focus
51      * is back to the view.
52      *
53      */
54     @RestrictTo(LIBRARY_GROUP_PREFIX)
55     public static final int FOCUS_SCROLL_ALIGNED = 0;
56 
57     /**
58      * Scroll to make the focused item inside client area.
59      *
60      */
61     @RestrictTo(LIBRARY_GROUP_PREFIX)
62     public static final int FOCUS_SCROLL_ITEM = 1;
63 
64     /**
65      * Scroll a page of items when focusing to item outside the client area.
66      * The page size matches the client area size of RecyclerView.
67      *
68      */
69     @RestrictTo(LIBRARY_GROUP_PREFIX)
70     public static final int FOCUS_SCROLL_PAGE = 2;
71 
72     /**
73      * The first item is aligned with the low edge of the viewport. When
74      * navigating away from the first item, the focus item is aligned to a key line location.
75      * <p>
76      * For HorizontalGridView, low edge refers to getPaddingLeft() when RTL is false or
77      * getWidth() - getPaddingRight() when RTL is true.
78      * For VerticalGridView, low edge refers to getPaddingTop().
79      * <p>
80      * The key line location is calculated by "windowAlignOffset" and
81      * "windowAlignOffsetPercent"; if neither of these two is defined, the
82      * default value is 1/2 of the size.
83      * <p>
84      * Note if there are very few items between low edge and key line, use
85      * {@link #setWindowAlignmentPreferKeyLineOverLowEdge(boolean)} to control whether you prefer
86      * to align the items to key line or low edge. Default is preferring low edge.
87      */
88     public static final int WINDOW_ALIGN_LOW_EDGE = 1;
89 
90     /**
91      * The last item is aligned with the high edge of the viewport when
92      * navigating to the end of list. When navigating away from the end, the
93      * focus item is aligned to a key line location.
94      * <p>
95      * For HorizontalGridView, high edge refers to getWidth() - getPaddingRight() when RTL is false
96      * or getPaddingLeft() when RTL is true.
97      * For VerticalGridView, high edge refers to getHeight() - getPaddingBottom().
98      * <p>
99      * The key line location is calculated by "windowAlignOffset" and
100      * "windowAlignOffsetPercent"; if neither of these two is defined, the
101      * default value is 1/2 of the size.
102      * <p>
103      * Note if there are very few items between high edge and key line, use
104      * {@link #setWindowAlignmentPreferKeyLineOverHighEdge(boolean)} to control whether you prefer
105      * to align the items to key line or high edge. Default is preferring key line.
106      */
107     public static final int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
108 
109     /**
110      * The first item and last item are aligned with the two edges of the
111      * viewport. When navigating in the middle of list, the focus maintains a
112      * key line location.
113      * <p>
114      * The key line location is calculated by "windowAlignOffset" and
115      * "windowAlignOffsetPercent"; if neither of these two is defined, the
116      * default value is 1/2 of the size.
117      */
118     public static final int WINDOW_ALIGN_BOTH_EDGE =
119             WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
120 
121     /**
122      * The focused item always stays in a key line location.
123      * <p>
124      * The key line location is calculated by "windowAlignOffset" and
125      * "windowAlignOffsetPercent"; if neither of these two is defined, the
126      * default value is 1/2 of the size.
127      */
128     public static final int WINDOW_ALIGN_NO_EDGE = 0;
129 
130     /**
131      * Value indicates that percent is not used.
132      */
133     public static final float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
134 
135     /**
136      * Value indicates that percent is not used.
137      */
138     public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED =
139             ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
140 
141     /**
142      * Dont save states of any child views.
143      */
144     public static final int SAVE_NO_CHILD = 0;
145 
146     /**
147      * Only save on screen child views, the states are lost when they become off screen.
148      */
149     public static final int SAVE_ON_SCREEN_CHILD = 1;
150 
151     /**
152      * Save on screen views plus save off screen child views states up to
153      * {@link #getSaveChildrenLimitNumber()}.
154      */
155     public static final int SAVE_LIMITED_CHILD = 2;
156 
157     /**
158      * Save on screen views plus save off screen child views without any limitation.
159      * This might cause out of memory, only use it when you are dealing with limited data.
160      */
161     public static final int SAVE_ALL_CHILD = 3;
162 
163     private static final int PFLAG_RETAIN_FOCUS_FOR_CHILD = 1;
164 
165     /**
166      * Defines behavior of duration and interpolator for smoothScrollBy().
167      */
168     public interface SmoothScrollByBehavior {
169         /**
170          * Defines duration in milliseconds of smoothScrollBy().
171          *
172          * @param dx x distance in pixels.
173          * @param dy y distance in pixels.
174          * @return Duration in milliseconds or UNDEFINED_DURATION for default value.
175          */
configSmoothScrollByDuration(int dx, int dy)176         int configSmoothScrollByDuration(int dx, int dy);
177 
178         /**
179          * Defines interpolator of smoothScrollBy().
180          *
181          * @param dx x distance in pixels.
182          * @param dy y distance in pixels.
183          * @return Interpolator to be used or null for default interpolator.
184          */
configSmoothScrollByInterpolator(int dx, int dy)185         @Nullable Interpolator configSmoothScrollByInterpolator(int dx, int dy);
186     }
187 
188     /**
189      * Listener for intercepting touch dispatch events.
190      */
191     public interface OnTouchInterceptListener {
192         /**
193          * Returns true if the touch dispatch event should be consumed.
194          */
onInterceptTouchEvent(@onNull MotionEvent event)195         boolean onInterceptTouchEvent(@NonNull MotionEvent event);
196     }
197 
198     /**
199      * Listener for intercepting generic motion dispatch events.
200      */
201     public interface OnMotionInterceptListener {
202         /**
203          * Returns true if the touch dispatch event should be consumed.
204          */
onInterceptMotionEvent(@onNull MotionEvent event)205         boolean onInterceptMotionEvent(@NonNull MotionEvent event);
206     }
207 
208     /**
209      * Listener for intercepting key dispatch events.
210      */
211     public interface OnKeyInterceptListener {
212         /**
213          * Returns true if the key dispatch event should be consumed.
214          */
onInterceptKeyEvent(@onNull KeyEvent event)215         boolean onInterceptKeyEvent(@NonNull KeyEvent event);
216     }
217 
218     /**
219      * Listener for intercepting unhandled key events.
220      */
221     public interface OnUnhandledKeyListener {
222         /**
223          * Returns true if the key event should be consumed.
224          */
onUnhandledKey(@onNull KeyEvent event)225         boolean onUnhandledKey(@NonNull KeyEvent event);
226     }
227 
228     /**
229      * Interface for receiving notification when BaseGridView has completed a full layout
230      * calculation.
231      */
232     public interface OnLayoutCompletedListener {
233 
234         /**
235          * Called after a full layout calculation is finished.
236          *
237          * @param state Transient state of RecyclerView
238          */
onLayoutCompleted(RecyclerView.@onNull State state)239         void onLayoutCompleted(RecyclerView.@NonNull State state);
240     }
241 
242     GridLayoutManager mLayoutManager;
243 
244     private SmoothScrollByBehavior mSmoothScrollByBehavior;
245 
246     /**
247      * Animate layout changes from a child resizing or adding/removing a child.
248      */
249     private boolean mAnimateChildLayout = true;
250 
251     private boolean mHasOverlappingRendering = true;
252 
253     private RecyclerView.ItemAnimator mSavedItemAnimator;
254 
255     private OnTouchInterceptListener mOnTouchInterceptListener;
256     private OnMotionInterceptListener mOnMotionInterceptListener;
257     private OnKeyInterceptListener mOnKeyInterceptListener;
258     private OnUnhandledKeyListener mOnUnhandledKeyListener;
259 
260     /**
261      * Number of items to prefetch when first coming on screen with new data.
262      */
263     int mInitialPrefetchItemCount = 4;
264 
265     private int mPrivateFlag;
266 
BaseGridView(Context context, AttributeSet attrs, int defStyle)267     BaseGridView(Context context, AttributeSet attrs, int defStyle) {
268         super(context, attrs, defStyle);
269         mLayoutManager = new GridLayoutManager(this);
270         setLayoutManager(mLayoutManager);
271         // leanback LayoutManager already restores focus inside onLayoutChildren().
272         setPreserveFocusAfterLayout(false);
273         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
274         setHasFixedSize(true);
275         setChildrenDrawingOrderEnabled(true);
276         setWillNotDraw(true);
277         setOverScrollMode(View.OVER_SCROLL_NEVER);
278         // Disable change animation by default on leanback.
279         // Change animation will create a new view and cause undesired
280         // focus animation between the old view and new view.
281         ((SimpleItemAnimator) getItemAnimator()).setSupportsChangeAnimations(false);
282         super.addRecyclerListener(new RecyclerView.RecyclerListener() {
283             @Override
284             public void onViewRecycled(RecyclerView.@NonNull ViewHolder holder) {
285                 mLayoutManager.onChildRecycled(holder);
286             }
287         });
288     }
289 
290     @SuppressLint("CustomViewStyleable")
initBaseGridViewAttributes(Context context, AttributeSet attrs)291     void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
292         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
293         boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
294         boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
295         mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
296         boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true);
297         boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true);
298         mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd);
299         mLayoutManager.setVerticalSpacing(
300                 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_verticalSpacing,
301                         a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0)));
302         mLayoutManager.setHorizontalSpacing(
303                 a.getDimensionPixelSize(R.styleable.lbBaseGridView_android_horizontalSpacing,
304                         a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0)));
305         if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
306             setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
307         }
308         a.recycle();
309     }
310 
311     /**
312      * Sets the strategy used to scroll in response to item focus changing:
313      * <ul>
314      * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
315      * <li>{@link #FOCUS_SCROLL_ITEM}</li>
316      * <li>{@link #FOCUS_SCROLL_PAGE}</li>
317      * </ul>
318      *
319      */
320     @RestrictTo(LIBRARY_GROUP_PREFIX)
setFocusScrollStrategy(int scrollStrategy)321     public void setFocusScrollStrategy(int scrollStrategy) {
322         if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
323                 && scrollStrategy != FOCUS_SCROLL_PAGE) {
324             throw new IllegalArgumentException("Invalid scrollStrategy");
325         }
326         mLayoutManager.setFocusScrollStrategy(scrollStrategy);
327         requestLayout();
328     }
329 
330     /**
331      * Returns the strategy used to scroll in response to item focus changing.
332      * <ul>
333      * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
334      * <li>{@link #FOCUS_SCROLL_ITEM}</li>
335      * <li>{@link #FOCUS_SCROLL_PAGE}</li>
336      * </ul>
337      *
338      */
339     @RestrictTo(LIBRARY_GROUP_PREFIX)
getFocusScrollStrategy()340     public int getFocusScrollStrategy() {
341         return mLayoutManager.getFocusScrollStrategy();
342     }
343 
344     /**
345      * Sets the method for focused item alignment in the view.
346      *
347      * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
348      *                        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
349      *                        {@link #WINDOW_ALIGN_NO_EDGE}.
350      */
setWindowAlignment(int windowAlignment)351     public void setWindowAlignment(int windowAlignment) {
352         mLayoutManager.setWindowAlignment(windowAlignment);
353         requestLayout();
354     }
355 
356     /**
357      * Returns the method for focused item alignment in the view.
358      *
359      * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
360      * {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
361      */
getWindowAlignment()362     public int getWindowAlignment() {
363         return mLayoutManager.getWindowAlignment();
364     }
365 
366     /**
367      * Sets whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
368      * When true, if there are very few items between low edge and key line, align items to key
369      * line instead of align items to low edge.
370      * Default value is false (aka prefer align to low edge).
371      *
372      * @param preferKeyLineOverLowEdge True to prefer key line over low edge, false otherwise.
373      */
setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge)374     public void setWindowAlignmentPreferKeyLineOverLowEdge(boolean preferKeyLineOverLowEdge) {
375         mLayoutManager.mWindowAlignment.mainAxis()
376                 .setPreferKeylineOverLowEdge(preferKeyLineOverLowEdge);
377         requestLayout();
378     }
379 
380 
381     /**
382      * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
383      * When true, if there are very few items between high edge and key line, align items to key
384      * line instead of align items to high edge.
385      * Default value is true (aka prefer align to key line).
386      *
387      * @param preferKeyLineOverHighEdge True to prefer key line over high edge, false otherwise.
388      */
setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge)389     public void setWindowAlignmentPreferKeyLineOverHighEdge(boolean preferKeyLineOverHighEdge) {
390         mLayoutManager.mWindowAlignment.mainAxis()
391                 .setPreferKeylineOverHighEdge(preferKeyLineOverHighEdge);
392         requestLayout();
393     }
394 
395     /**
396      * Returns whether prefer key line over low edge when {@link #WINDOW_ALIGN_LOW_EDGE} is used.
397      * When true, if there are very few items between low edge and key line, align items to key
398      * line instead of align items to low edge.
399      * Default value is false (aka prefer align to low edge).
400      *
401      * @return True to prefer key line over low edge, false otherwise.
402      */
isWindowAlignmentPreferKeyLineOverLowEdge()403     public boolean isWindowAlignmentPreferKeyLineOverLowEdge() {
404         return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverLowEdge();
405     }
406 
407 
408     /**
409      * Returns whether prefer key line over high edge when {@link #WINDOW_ALIGN_HIGH_EDGE} is used.
410      * When true, if there are very few items between high edge and key line, align items to key
411      * line instead of align items to high edge.
412      * Default value is true (aka prefer align to key line).
413      *
414      * @return True to prefer key line over high edge, false otherwise.
415      */
isWindowAlignmentPreferKeyLineOverHighEdge()416     public boolean isWindowAlignmentPreferKeyLineOverHighEdge() {
417         return mLayoutManager.mWindowAlignment.mainAxis().isPreferKeylineOverHighEdge();
418     }
419 
420 
421     /**
422      * Sets the offset in pixels for window alignment key line.
423      *
424      * @param offset The number of pixels to offset.  If the offset is positive,
425      *               it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
426      *               if the offset is negative, the absolute value is distance from high
427      *               edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
428      *               Default value is 0.
429      */
setWindowAlignmentOffset(int offset)430     public void setWindowAlignmentOffset(int offset) {
431         mLayoutManager.setWindowAlignmentOffset(offset);
432         requestLayout();
433     }
434 
435     /**
436      * Returns the offset in pixels for window alignment key line.
437      *
438      * @return The number of pixels to offset.  If the offset is positive,
439      * it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
440      * if the offset is negative, the absolute value is distance from high
441      * edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
442      * Default value is 0.
443      */
getWindowAlignmentOffset()444     public int getWindowAlignmentOffset() {
445         return mLayoutManager.getWindowAlignmentOffset();
446     }
447 
448     /**
449      * Sets the offset percent for window alignment key line in addition to {@link
450      * #getWindowAlignmentOffset()}.
451      *
452      * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
453      *                      width from low edge. Use
454      *                      {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
455      *                      Default value is 50.
456      */
setWindowAlignmentOffsetPercent(float offsetPercent)457     public void setWindowAlignmentOffsetPercent(float offsetPercent) {
458         mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
459         requestLayout();
460     }
461 
462     /**
463      * Returns the offset percent for window alignment key line in addition to
464      * {@link #getWindowAlignmentOffset()}.
465      *
466      * @return Percentage to offset. E.g., 40 means 40% of the width from the
467      * low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
468      * disabled. Default value is 50.
469      */
getWindowAlignmentOffsetPercent()470     public float getWindowAlignmentOffsetPercent() {
471         return mLayoutManager.getWindowAlignmentOffsetPercent();
472     }
473 
474     /**
475      * Sets number of pixels to the end of low edge. Supports right to left layout direction.
476      * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
477      * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
478      *
479      * @param offset In left to right or vertical case, it's the offset added to left/top edge.
480      *               In right to left case, it's the offset subtracted from right edge.
481      */
setItemAlignmentOffset(int offset)482     public void setItemAlignmentOffset(int offset) {
483         mLayoutManager.setItemAlignmentOffset(offset);
484         requestLayout();
485     }
486 
487     /**
488      * Returns number of pixels to the end of low edge. Supports right to left layout direction. In
489      * left to right or vertical case, it's the offset added to left/top edge. In right to left
490      * case, it's the offset subtracted from right edge.
491      * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
492      * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
493      *
494      * @return The number of pixels to the end of low edge.
495      */
getItemAlignmentOffset()496     public int getItemAlignmentOffset() {
497         return mLayoutManager.getItemAlignmentOffset();
498     }
499 
500     /**
501      * Sets whether applies padding to item alignment when {@link #getItemAlignmentOffsetPercent()}
502      * is 0 or 100.
503      * <p>When true:
504      * Applies start/top padding if {@link #getItemAlignmentOffsetPercent()} is 0.
505      * Applies end/bottom padding if {@link #getItemAlignmentOffsetPercent()} is 100.
506      * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
507      * </p>
508      * <p>When false: does not apply padding</p>
509      */
setItemAlignmentOffsetWithPadding(boolean withPadding)510     public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
511         mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
512         requestLayout();
513     }
514 
515     /**
516      * Returns true if applies padding to item alignment when
517      * {@link #getItemAlignmentOffsetPercent()} is 0 or 100; returns false otherwise.
518      * <p>When true:
519      * Applies start/top padding when {@link #getItemAlignmentOffsetPercent()} is 0.
520      * Applies end/bottom padding when {@link #getItemAlignmentOffsetPercent()} is 100.
521      * Does not apply padding if {@link #getItemAlignmentOffsetPercent()} is neither 0 nor 100.
522      * </p>
523      * <p>When false: does not apply padding</p>
524      */
isItemAlignmentOffsetWithPadding()525     public boolean isItemAlignmentOffsetWithPadding() {
526         return mLayoutManager.isItemAlignmentOffsetWithPadding();
527     }
528 
529     /**
530      * Sets the offset percent for item alignment in addition to {@link
531      * #getItemAlignmentOffset()}.
532      * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
533      * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
534      *
535      * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
536      *                      width from the low edge. Use
537      *                      {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
538      */
setItemAlignmentOffsetPercent(float offsetPercent)539     public void setItemAlignmentOffsetPercent(float offsetPercent) {
540         mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
541         requestLayout();
542     }
543 
544     /**
545      * Returns the offset percent for item alignment in addition to {@link
546      * #getItemAlignmentOffset()}.
547      *
548      * @return Percentage to offset. E.g., 40 means 40% of the width from the
549      * low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
550      * disabled. Default value is 50.
551      */
getItemAlignmentOffsetPercent()552     public float getItemAlignmentOffsetPercent() {
553         return mLayoutManager.getItemAlignmentOffsetPercent();
554     }
555 
556     /**
557      * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default)
558      * for the root {@link RecyclerView.ViewHolder#itemView}.
559      * Item alignment settings on BaseGridView are if {@link ItemAlignmentFacet}
560      * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
561      */
setItemAlignmentViewId(int viewId)562     public void setItemAlignmentViewId(int viewId) {
563         mLayoutManager.setItemAlignmentViewId(viewId);
564     }
565 
566     /**
567      * Returns the id of the view to align with, or {@link android.view.View#NO_ID} for the root
568      * {@link RecyclerView.ViewHolder#itemView}.
569      *
570      * @return The id of the view to align with, or {@link android.view.View#NO_ID} for the root
571      * {@link RecyclerView.ViewHolder#itemView}.
572      */
getItemAlignmentViewId()573     public int getItemAlignmentViewId() {
574         return mLayoutManager.getItemAlignmentViewId();
575     }
576 
577     /**
578      * Sets the spacing in pixels between two child items.
579      *
580      * @deprecated use {@link #setItemSpacing(int)}
581      */
582     @Deprecated
setItemMargin(int margin)583     public void setItemMargin(int margin) {
584         setItemSpacing(margin);
585     }
586 
587     /**
588      * Sets the vertical and horizontal spacing in pixels between two child items.
589      *
590      * @param spacing Vertical and horizontal spacing in pixels between two child items.
591      */
setItemSpacing(int spacing)592     public void setItemSpacing(int spacing) {
593         mLayoutManager.setItemSpacing(spacing);
594         requestLayout();
595     }
596 
597     /**
598      * Sets the spacing in pixels between two child items vertically.
599      *
600      * @deprecated Use {@link #setVerticalSpacing(int)}
601      */
602     @Deprecated
setVerticalMargin(int margin)603     public void setVerticalMargin(int margin) {
604         setVerticalSpacing(margin);
605     }
606 
607     /**
608      * Returns the spacing in pixels between two child items vertically.
609      *
610      * @deprecated Use {@link #getVerticalSpacing()}
611      */
612     @Deprecated
getVerticalMargin()613     public int getVerticalMargin() {
614         return mLayoutManager.getVerticalSpacing();
615     }
616 
617     /**
618      * Sets the spacing in pixels between two child items horizontally.
619      *
620      * @deprecated Use {@link #setHorizontalSpacing(int)}
621      */
622     @Deprecated
setHorizontalMargin(int margin)623     public void setHorizontalMargin(int margin) {
624         setHorizontalSpacing(margin);
625     }
626 
627     /**
628      * Returns the spacing in pixels between two child items horizontally.
629      *
630      * @deprecated Use {@link #getHorizontalSpacing()}
631      */
632     @Deprecated
getHorizontalMargin()633     public int getHorizontalMargin() {
634         return mLayoutManager.getHorizontalSpacing();
635     }
636 
637     /**
638      * Sets the vertical spacing in pixels between two child items.
639      *
640      * @param spacing Vertical spacing between two child items.
641      */
setVerticalSpacing(int spacing)642     public void setVerticalSpacing(int spacing) {
643         mLayoutManager.setVerticalSpacing(spacing);
644         requestLayout();
645     }
646 
647     /**
648      * Returns the vertical spacing in pixels between two child items.
649      *
650      * @return The vertical spacing in pixels between two child items.
651      */
getVerticalSpacing()652     public int getVerticalSpacing() {
653         return mLayoutManager.getVerticalSpacing();
654     }
655 
656     /**
657      * Sets the horizontal spacing in pixels between two child items.
658      *
659      * @param spacing Horizontal spacing in pixels between two child items.
660      */
setHorizontalSpacing(int spacing)661     public void setHorizontalSpacing(int spacing) {
662         mLayoutManager.setHorizontalSpacing(spacing);
663         requestLayout();
664     }
665 
666     /**
667      * Returns the horizontal spacing in pixels between two child items.
668      *
669      * @return The Horizontal spacing in pixels between two child items.
670      */
getHorizontalSpacing()671     public int getHorizontalSpacing() {
672         return mLayoutManager.getHorizontalSpacing();
673     }
674 
675     /**
676      * Registers a callback to be invoked when an item in BaseGridView has
677      * been laid out.
678      *
679      * @param listener The listener to be invoked.
680      */
setOnChildLaidOutListener(@ullable OnChildLaidOutListener listener)681     public void setOnChildLaidOutListener(@Nullable OnChildLaidOutListener listener) {
682         mLayoutManager.setOnChildLaidOutListener(listener);
683     }
684 
685     /**
686      * Registers a callback to be invoked when an item in BaseGridView has
687      * been selected.  Note that the listener may be invoked when there is a
688      * layout pending on the view, affording the listener an opportunity to
689      * adjust the upcoming layout based on the selection state.
690      *
691      * @param listener The listener to be invoked.
692      */
693     @SuppressLint("ReferencesDeprecated")
694     @SuppressWarnings("deprecation")
setOnChildSelectedListener(@ullable OnChildSelectedListener listener)695     public void setOnChildSelectedListener(@Nullable OnChildSelectedListener listener) {
696         mLayoutManager.setOnChildSelectedListener(listener);
697     }
698 
699     /**
700      * Registers a callback to be invoked when the BaseGridView completes a full layout calculation.
701      *
702      * @param listener The listener to be invoked.
703      */
addOnLayoutCompletedListener(@onNull OnLayoutCompletedListener listener)704     public final void addOnLayoutCompletedListener(@NonNull OnLayoutCompletedListener listener) {
705         mLayoutManager.addOnLayoutCompletedListener(listener);
706     }
707 
708     /**
709      * Removes a callback to be invoked when the BaseGridView completes a full layout calculation.
710      *
711      * @param listener The listener to be invoked.
712      */
removeOnLayoutCompletedListener(@onNull OnLayoutCompletedListener listener)713     public final void removeOnLayoutCompletedListener(@NonNull OnLayoutCompletedListener listener) {
714         mLayoutManager.removeOnLayoutCompletedListener(listener);
715     }
716 
717     /**
718      * Registers a callback to be invoked when an item in BaseGridView has
719      * been selected.  Note that the listener may be invoked when there is a
720      * layout pending on the view, affording the listener an opportunity to
721      * adjust the upcoming layout based on the selection state.
722      * This method will clear all existing listeners added by
723      * {@link #addOnChildViewHolderSelectedListener}.
724      *
725      * @param listener The listener to be invoked.
726      */
setOnChildViewHolderSelectedListener( @ullable OnChildViewHolderSelectedListener listener)727     public void setOnChildViewHolderSelectedListener(
728             @Nullable OnChildViewHolderSelectedListener listener) {
729         mLayoutManager.setOnChildViewHolderSelectedListener(listener);
730     }
731 
732     /**
733      * Registers a callback to be invoked when an item in BaseGridView has
734      * been selected.  Note that the listener may be invoked when there is a
735      * layout pending on the view, affording the listener an opportunity to
736      * adjust the upcoming layout based on the selection state.
737      *
738      * @param listener The listener to be invoked.
739      */
addOnChildViewHolderSelectedListener( @onNull OnChildViewHolderSelectedListener listener)740     public void addOnChildViewHolderSelectedListener(
741             @NonNull OnChildViewHolderSelectedListener listener) {
742         mLayoutManager.addOnChildViewHolderSelectedListener(listener);
743     }
744 
745     /**
746      * Remove the callback invoked when an item in BaseGridView has been selected.
747      *
748      * @param listener The listener to be removed.
749      */
removeOnChildViewHolderSelectedListener( @onNull OnChildViewHolderSelectedListener listener)750     public void removeOnChildViewHolderSelectedListener(
751             @NonNull OnChildViewHolderSelectedListener listener) {
752         mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
753     }
754 
755     /**
756      * Changes the selected item immediately without animation.
757      */
setSelectedPosition(int position)758     public void setSelectedPosition(int position) {
759         mLayoutManager.setSelection(position, 0);
760     }
761 
762     /**
763      * Changes the selected item and/or subposition immediately without animation.
764      *
765      */
766     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSelectedPositionWithSub(int position, int subposition)767     public void setSelectedPositionWithSub(int position, int subposition) {
768         mLayoutManager.setSelectionWithSub(position, subposition, 0);
769     }
770 
771     /**
772      * Changes the selected item immediately without animation, scrollExtra is
773      * applied in primary scroll direction.  The scrollExtra will be kept until
774      * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
775      */
setSelectedPosition(int position, int scrollExtra)776     public void setSelectedPosition(int position, int scrollExtra) {
777         mLayoutManager.setSelection(position, scrollExtra);
778     }
779 
780     /**
781      * Changes the selected item and/or subposition immediately without animation, scrollExtra is
782      * applied in primary scroll direction.  The scrollExtra will be kept until
783      * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
784      *
785      */
786     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSelectedPositionWithSub(int position, int subposition, int scrollExtra)787     public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
788         mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
789     }
790 
791     /**
792      * Changes the selected item and run an animation to scroll to the target
793      * position.
794      *
795      * @param position Adapter position of the item to select.
796      */
setSelectedPositionSmooth(int position)797     public void setSelectedPositionSmooth(int position) {
798         mLayoutManager.setSelectionSmooth(position);
799     }
800 
801     /**
802      * Changes the selected item and/or subposition, runs an animation to scroll to the target
803      * position.
804      *
805      */
806     @RestrictTo(LIBRARY_GROUP_PREFIX)
setSelectedPositionSmoothWithSub(int position, int subposition)807     public void setSelectedPositionSmoothWithSub(int position, int subposition) {
808         mLayoutManager.setSelectionSmoothWithSub(position, subposition);
809     }
810 
811     /**
812      * Perform a task on ViewHolder at given position after smooth scrolling to it.
813      *
814      * @param position Position of item in adapter.
815      * @param task     Task to executed on the ViewHolder at a given position.
816      */
817     @SuppressWarnings("deprecation")
setSelectedPositionSmooth(final int position, final @Nullable ViewHolderTask task)818     public void setSelectedPositionSmooth(final int position, final @Nullable ViewHolderTask task) {
819         if (task != null) {
820             RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
821             if (vh == null || hasPendingAdapterUpdates()) {
822                 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
823                     @Override
824                     public void onChildViewHolderSelected(@NonNull RecyclerView parent,
825                             RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
826                         if (selectedPosition == position) {
827                             removeOnChildViewHolderSelectedListener(this);
828                             task.run(child);
829                         }
830                     }
831                 });
832             } else {
833                 task.run(vh);
834             }
835         }
836         setSelectedPositionSmooth(position);
837     }
838 
839     /**
840      * Perform a task on ViewHolder at given position after scroll to it.
841      *
842      * @param position Position of item in adapter.
843      * @param task     Task to executed on the ViewHolder at a given position.
844      */
845     @SuppressWarnings("deprecation")
setSelectedPosition(final int position, final @Nullable ViewHolderTask task)846     public void setSelectedPosition(final int position, final @Nullable ViewHolderTask task) {
847         if (task != null) {
848             RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
849             if (vh == null || hasPendingAdapterUpdates()) {
850                 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
851                     @Override
852                     public void onChildViewHolderSelectedAndPositioned(@NonNull RecyclerView parent,
853                             RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
854                         if (selectedPosition == position) {
855                             removeOnChildViewHolderSelectedListener(this);
856                             task.run(child);
857                         }
858                     }
859                 });
860             } else {
861                 task.run(vh);
862             }
863         }
864         setSelectedPosition(position);
865     }
866 
867     /**
868      * Returns the adapter position of selected item.
869      *
870      * @return The adapter position of selected item.
871      */
getSelectedPosition()872     public int getSelectedPosition() {
873         return mLayoutManager.getSelection();
874     }
875 
876     /**
877      * Returns the sub selected item position started from zero.  An item can have
878      * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
879      * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
880      * is defined.
881      *
882      */
883     @RestrictTo(LIBRARY_GROUP_PREFIX)
getSelectedSubPosition()884     public int getSelectedSubPosition() {
885         return mLayoutManager.getSubSelection();
886     }
887 
888     /**
889      * Sets whether ItemAnimator should run when a child changes size or when adding
890      * or removing a child.
891      *
892      * @param animateChildLayout True to enable ItemAnimator, false to disable.
893      */
setAnimateChildLayout(boolean animateChildLayout)894     public void setAnimateChildLayout(boolean animateChildLayout) {
895         if (mAnimateChildLayout != animateChildLayout) {
896             mAnimateChildLayout = animateChildLayout;
897             if (!mAnimateChildLayout) {
898                 mSavedItemAnimator = getItemAnimator();
899                 super.setItemAnimator(null);
900             } else {
901                 super.setItemAnimator(mSavedItemAnimator);
902             }
903         }
904     }
905 
906     /**
907      * Returns true if an animation will run when a child changes size or when
908      * adding or removing a child.
909      *
910      * @return True if ItemAnimator is enabled, false otherwise.
911      */
isChildLayoutAnimated()912     public boolean isChildLayoutAnimated() {
913         return mAnimateChildLayout;
914     }
915 
916     /**
917      * Sets the gravity used for child view positioning. Defaults to
918      * GRAVITY_TOP|GRAVITY_START.
919      *
920      * @param gravity See {@link android.view.Gravity}
921      */
setGravity(int gravity)922     public void setGravity(int gravity) {
923         mLayoutManager.setGravity(gravity);
924         requestLayout();
925     }
926 
927     @Override
setLayoutManager(RecyclerView.@ullable LayoutManager layout)928     public void setLayoutManager(RecyclerView.@Nullable LayoutManager layout) {
929         if (layout == null) {
930             super.setLayoutManager(null);
931             if (mLayoutManager != null) {
932                 mLayoutManager.setGridView(null);
933             }
934             mLayoutManager = null;
935             return;
936         }
937 
938         mLayoutManager = (GridLayoutManager) layout;
939         mLayoutManager.setGridView(this);
940         super.setLayoutManager(layout);
941     }
942 
943     @Override
onRequestFocusInDescendants(int direction, @Nullable Rect previouslyFocusedRect)944     public boolean onRequestFocusInDescendants(int direction,
945             @Nullable Rect previouslyFocusedRect) {
946         if ((mPrivateFlag & PFLAG_RETAIN_FOCUS_FOR_CHILD) == PFLAG_RETAIN_FOCUS_FOR_CHILD) {
947             // dont focus to child if GridView itself retains focus for child
948             return false;
949         }
950         return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
951                 previouslyFocusedRect);
952     }
953 
954     /**
955      * Returns the x/y offsets to final position from current position if the view
956      * is selected.
957      *
958      * @param view    The view to get offsets.
959      * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
960      */
getViewSelectedOffsets(@onNull View view, int @NonNull [] offsets)961     public void getViewSelectedOffsets(@NonNull View view, int @NonNull [] offsets) {
962         mLayoutManager.getViewSelectedOffsets(view, offsets);
963     }
964 
965     @Override
getChildDrawingOrder(int childCount, int i)966     public int getChildDrawingOrder(int childCount, int i) {
967         return mLayoutManager.getChildDrawingOrder(this, childCount, i);
968     }
969 
isChildrenDrawingOrderEnabledInternal()970     final boolean isChildrenDrawingOrderEnabledInternal() {
971         return isChildrenDrawingOrderEnabled();
972     }
973 
974     @Override
focusSearch(int direction)975     public @Nullable View focusSearch(int direction) {
976         if (isFocused()) {
977             // focusSearch(int) is called when GridView itself is focused.
978             // Calling focusSearch(view, int) to get next sibling of current selected child.
979             View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
980             if (view != null) {
981                 return focusSearch(view, direction);
982             }
983         }
984         // otherwise, go to mParent to perform focusSearch
985         return super.focusSearch(direction);
986     }
987 
988     @Override
onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect)989     protected void onFocusChanged(boolean gainFocus, int direction,
990             @Nullable Rect previouslyFocusedRect) {
991         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
992         mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
993     }
994 
995     /**
996      * Disables or enables focus search.
997      *
998      * @param disabled True to disable focus search, false to enable.
999      */
setFocusSearchDisabled(boolean disabled)1000     public final void setFocusSearchDisabled(boolean disabled) {
1001         // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
1002         // re-gain focus after a BACK key pressed, so block children focus during transition.
1003         setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS : FOCUS_AFTER_DESCENDANTS);
1004         mLayoutManager.setFocusSearchDisabled(disabled);
1005     }
1006 
1007     /**
1008      * Returns true if focus search is disabled.
1009      *
1010      * @return True if focus search is disabled.
1011      */
isFocusSearchDisabled()1012     public final boolean isFocusSearchDisabled() {
1013         return mLayoutManager.isFocusSearchDisabled();
1014     }
1015 
1016     /**
1017      * Enables or disables layout.  All children will be removed when layout is
1018      * disabled.
1019      *
1020      * @param layoutEnabled True to enable layout, false otherwise.
1021      */
setLayoutEnabled(boolean layoutEnabled)1022     public void setLayoutEnabled(boolean layoutEnabled) {
1023         mLayoutManager.setLayoutEnabled(layoutEnabled);
1024     }
1025 
1026     /**
1027      * Changes and overrides children's visibility.
1028      *
1029      * @param visibility See {@link View#getVisibility()}.
1030      */
setChildrenVisibility(int visibility)1031     public void setChildrenVisibility(int visibility) {
1032         mLayoutManager.setChildrenVisibility(visibility);
1033     }
1034 
1035     /**
1036      * Enables or disables pruning of children.  Disable is useful during transition.
1037      *
1038      * @param pruneChild True to prune children out side visible area, false to enable.
1039      */
setPruneChild(boolean pruneChild)1040     public void setPruneChild(boolean pruneChild) {
1041         mLayoutManager.setPruneChild(pruneChild);
1042     }
1043 
1044     /**
1045      * Enables or disables scrolling.  Disable is useful during transition.
1046      *
1047      * @param scrollEnabled True to enable scroll, false to disable.
1048      */
setScrollEnabled(boolean scrollEnabled)1049     public void setScrollEnabled(boolean scrollEnabled) {
1050         mLayoutManager.setScrollEnabled(scrollEnabled);
1051     }
1052 
1053     /**
1054      * Returns true if scrolling is enabled, false otherwise.
1055      *
1056      * @return True if scrolling is enabled, false otherwise.
1057      */
isScrollEnabled()1058     public boolean isScrollEnabled() {
1059         return mLayoutManager.isScrollEnabled();
1060     }
1061 
1062     /**
1063      * Returns true if the view at the given position has a same row sibling
1064      * in front of it.  This will return true if first item view is not created.
1065      *
1066      * @param position Position in adapter.
1067      * @return True if the view at the given position has a same row sibling in front of it.
1068      */
hasPreviousViewInSameRow(int position)1069     public boolean hasPreviousViewInSameRow(int position) {
1070         return mLayoutManager.hasPreviousViewInSameRow(position);
1071     }
1072 
1073     /**
1074      * Enables or disables the default "focus draw at last" order rule. Default is enabled.
1075      *
1076      * @param enabled True to draw the selected child at last, false otherwise.
1077      */
setFocusDrawingOrderEnabled(boolean enabled)1078     public void setFocusDrawingOrderEnabled(boolean enabled) {
1079         super.setChildrenDrawingOrderEnabled(enabled);
1080     }
1081 
1082     /**
1083      * Returns true if draws selected child at last, false otherwise. Default is enabled.
1084      *
1085      * @return True if draws selected child at last, false otherwise.
1086      */
isFocusDrawingOrderEnabled()1087     public boolean isFocusDrawingOrderEnabled() {
1088         return super.isChildrenDrawingOrderEnabled();
1089     }
1090 
1091     /**
1092      * Sets the touch intercept listener.
1093      *
1094      * @param listener The touch intercept listener.
1095      */
setOnTouchInterceptListener(@ullable OnTouchInterceptListener listener)1096     public void setOnTouchInterceptListener(@Nullable OnTouchInterceptListener listener) {
1097         mOnTouchInterceptListener = listener;
1098     }
1099 
1100     /**
1101      * Sets the generic motion intercept listener.
1102      *
1103      * @param listener The motion intercept listener.
1104      */
setOnMotionInterceptListener(@ullable OnMotionInterceptListener listener)1105     public void setOnMotionInterceptListener(@Nullable OnMotionInterceptListener listener) {
1106         mOnMotionInterceptListener = listener;
1107     }
1108 
1109     /**
1110      * Sets the key intercept listener.
1111      *
1112      * @param listener The key intercept listener.
1113      */
setOnKeyInterceptListener(@ullable OnKeyInterceptListener listener)1114     public void setOnKeyInterceptListener(@Nullable OnKeyInterceptListener listener) {
1115         mOnKeyInterceptListener = listener;
1116     }
1117 
1118     /**
1119      * Sets the unhandled key listener.
1120      *
1121      * @param listener The unhandled key intercept listener.
1122      */
setOnUnhandledKeyListener(@ullable OnUnhandledKeyListener listener)1123     public void setOnUnhandledKeyListener(@Nullable OnUnhandledKeyListener listener) {
1124         mOnUnhandledKeyListener = listener;
1125     }
1126 
1127     /**
1128      * Returns the unhandled key listener.
1129      *
1130      * @return The unhandled key listener.
1131      */
getOnUnhandledKeyListener()1132     public @Nullable OnUnhandledKeyListener getOnUnhandledKeyListener() {
1133         return mOnUnhandledKeyListener;
1134     }
1135 
1136     @Override
dispatchKeyEvent(@onNull KeyEvent event)1137     public boolean dispatchKeyEvent(@NonNull KeyEvent event) {
1138         if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
1139             return true;
1140         }
1141         if (super.dispatchKeyEvent(event)) {
1142             return true;
1143         }
1144         return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event);
1145     }
1146 
1147     @Override
dispatchTouchEvent(@onNull MotionEvent event)1148     public boolean dispatchTouchEvent(@NonNull MotionEvent event) {
1149         if (mOnTouchInterceptListener != null) {
1150             if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
1151                 return true;
1152             }
1153         }
1154         return super.dispatchTouchEvent(event);
1155     }
1156 
1157     @Override
dispatchGenericFocusedEvent(@onNull MotionEvent event)1158     protected boolean dispatchGenericFocusedEvent(@NonNull MotionEvent event) {
1159         if (mOnMotionInterceptListener != null) {
1160             if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
1161                 return true;
1162             }
1163         }
1164         return super.dispatchGenericFocusedEvent(event);
1165     }
1166 
1167     /**
1168      * Returns the policy for saving children.
1169      *
1170      * @return policy, one of {@link #SAVE_NO_CHILD}
1171      * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
1172      */
getSaveChildrenPolicy()1173     public final int getSaveChildrenPolicy() {
1174         return mLayoutManager.mChildrenStates.getSavePolicy();
1175     }
1176 
1177     /**
1178      * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
1179      * {@link #SAVE_LIMITED_CHILD}
1180      */
getSaveChildrenLimitNumber()1181     public final int getSaveChildrenLimitNumber() {
1182         return mLayoutManager.mChildrenStates.getLimitNumber();
1183     }
1184 
1185     /**
1186      * Sets the policy for saving children.
1187      *
1188      * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
1189      *                   {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
1190      */
setSaveChildrenPolicy(int savePolicy)1191     public final void setSaveChildrenPolicy(int savePolicy) {
1192         mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
1193     }
1194 
1195     /**
1196      * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
1197      */
setSaveChildrenLimitNumber(int limitNumber)1198     public final void setSaveChildrenLimitNumber(int limitNumber) {
1199         mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
1200     }
1201 
1202     @Override
hasOverlappingRendering()1203     public boolean hasOverlappingRendering() {
1204         return mHasOverlappingRendering;
1205     }
1206 
setHasOverlappingRendering(boolean hasOverlapping)1207     public void setHasOverlappingRendering(boolean hasOverlapping) {
1208         mHasOverlappingRendering = hasOverlapping;
1209     }
1210 
1211     /**
1212      * Notify layout manager that layout directionality has been updated
1213      */
1214     @Override
onRtlPropertiesChanged(int layoutDirection)1215     public void onRtlPropertiesChanged(int layoutDirection) {
1216         if (mLayoutManager != null) {
1217             mLayoutManager.onRtlPropertiesChanged(layoutDirection);
1218         }
1219     }
1220 
1221     /**
1222      * Sets pixels of extra space for layout child in invisible area.
1223      *
1224      * @param extraLayoutSpace Pixels of extra space for layout invisible child.
1225      *                         Must be bigger or equals to 0.
1226      */
1227     @RestrictTo(LIBRARY_GROUP_PREFIX)
setExtraLayoutSpace(int extraLayoutSpace)1228     public void setExtraLayoutSpace(int extraLayoutSpace) {
1229         mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
1230     }
1231 
1232     /**
1233      * Returns pixels of extra space for layout child in invisible area.
1234      *
1235      */
1236     @RestrictTo(LIBRARY_GROUP_PREFIX)
getExtraLayoutSpace()1237     public int getExtraLayoutSpace() {
1238         return mLayoutManager.getExtraLayoutSpace();
1239     }
1240 
1241     /**
1242      * Temporarily slide out child views to bottom (for VerticalGridView) or end
1243      * (for HorizontalGridView). Layout and scrolling will be suppressed until
1244      * {@link #animateIn()} is called.
1245      */
animateOut()1246     public void animateOut() {
1247         mLayoutManager.slideOut();
1248     }
1249 
1250     /**
1251      * Undo animateOut() and slide in child views.
1252      */
animateIn()1253     public void animateIn() {
1254         mLayoutManager.slideIn();
1255     }
1256 
1257     @Override
scrollToPosition(int position)1258     public void scrollToPosition(int position) {
1259         // dont abort the animateOut() animation, just record the position
1260         if (mLayoutManager.isSlidingChildViews()) {
1261             mLayoutManager.setSelectionWithSub(position, 0, 0);
1262             return;
1263         }
1264         super.scrollToPosition(position);
1265     }
1266 
1267     @Override
smoothScrollToPosition(int position)1268     public void smoothScrollToPosition(int position) {
1269         // dont abort the animateOut() animation, just record the position
1270         if (mLayoutManager.isSlidingChildViews()) {
1271             mLayoutManager.setSelectionWithSub(position, 0, 0);
1272             return;
1273         }
1274         super.smoothScrollToPosition(position);
1275     }
1276 
1277     /**
1278      * Set custom behavior for smoothScrollBy().
1279      *
1280      * @param behavior Custom behavior of SmoothScrollBy(). Null for default behavior.
1281      */
setSmoothScrollByBehavior(@ullable SmoothScrollByBehavior behavior)1282     public final void setSmoothScrollByBehavior(@Nullable SmoothScrollByBehavior behavior) {
1283         mSmoothScrollByBehavior = behavior;
1284     }
1285 
1286     /**
1287      * Returns custom behavior for smoothScrollBy().
1288      *
1289      * @return Custom behavior for SmoothScrollBy(). Null for default behavior.
1290      */
getSmoothScrollByBehavior()1291     public @Nullable SmoothScrollByBehavior getSmoothScrollByBehavior() {
1292         return mSmoothScrollByBehavior;
1293     }
1294 
1295     @Override
smoothScrollBy(int dx, int dy)1296     public void smoothScrollBy(int dx, int dy) {
1297         if (mSmoothScrollByBehavior != null) {
1298             smoothScrollBy(dx, dy,
1299                     mSmoothScrollByBehavior.configSmoothScrollByInterpolator(dx, dy),
1300                     mSmoothScrollByBehavior.configSmoothScrollByDuration(dx, dy));
1301         } else {
1302             smoothScrollBy(dx, dy, null, UNDEFINED_DURATION);
1303         }
1304     }
1305 
1306     @Override
smoothScrollBy(int dx, int dy, @Nullable Interpolator interpolator)1307     public void smoothScrollBy(int dx, int dy, @Nullable Interpolator interpolator) {
1308         if (mSmoothScrollByBehavior != null) {
1309             smoothScrollBy(dx, dy,
1310                     interpolator,
1311                     mSmoothScrollByBehavior.configSmoothScrollByDuration(dx, dy));
1312         } else {
1313             smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION);
1314         }
1315     }
1316 
1317     /**
1318      * Set factor of how slow the smoothScroller should run. For example when set to 2f, the smooth
1319      * scroller is twice slower. The value is 1f by default.
1320      *
1321      * @param smoothScrollSpeedFactor Factor of how slow the smooth scroll is.
1322      */
setSmoothScrollSpeedFactor(float smoothScrollSpeedFactor)1323     public final void setSmoothScrollSpeedFactor(float smoothScrollSpeedFactor) {
1324         mLayoutManager.mSmoothScrollSpeedFactor = smoothScrollSpeedFactor;
1325     }
1326 
1327     /**
1328      * @return Factor of how slow the smoothScroller runs. Default value is 1f.
1329      */
getSmoothScrollSpeedFactor()1330     public final float getSmoothScrollSpeedFactor() {
1331         return mLayoutManager.mSmoothScrollSpeedFactor;
1332     }
1333 
1334     /**
1335      * When holding DPAD, DPAD events are generated faster than the grid view can scroll. The
1336      * grid view counts unhandled DPAD events and completes the movement after user release DPAD.
1337      * If the value is set too high, the scrolling will last very long after DPAD is released. If
1338      * the value is set too low, it may miss many DPAD events. The default value is 10. If app
1339      * increases {@link #setSmoothScrollSpeedFactor(float)}, it may need decrease the max pending
1340      * DPAD events to avoid scrolling too long after DPAD release.
1341      *
1342      * @param maxPendingMoves Maximum number of pending DPAD events to be remembered.
1343      */
setSmoothScrollMaxPendingMoves(int maxPendingMoves)1344     public final void setSmoothScrollMaxPendingMoves(int maxPendingMoves) {
1345         mLayoutManager.mMaxPendingMoves = maxPendingMoves;
1346     }
1347 
1348     /**
1349      * When holding DPAD, DPAD events are generated faster than the grid view can scroll. The
1350      * grid view counts unhandled DPAD events and complete the movement after user release DPAD.
1351      * If the value is set too high, the scrolling will last very long after DPAD is released. If
1352      * the value is set too low, it may miss many DPAD events. The default value is 10. If app
1353      * increases {@link #setSmoothScrollSpeedFactor(float)}, it may need decrease the max pending
1354      * DPAD events to avoid scrolling too long after DPAD release.
1355      *
1356      * @return Maximum number of pending DPAD events to be remembered when smooth scroll cannot
1357      * catch up speed of DPAD events being sent.
1358      */
getSmoothScrollMaxPendingMoves()1359     public final int getSmoothScrollMaxPendingMoves() {
1360         return mLayoutManager.mMaxPendingMoves;
1361     }
1362 
1363     /**
1364      * Sets the number of items to prefetch in
1365      * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
1366      * which defines how many inner items should be prefetched when this GridView is nested inside
1367      * another RecyclerView.
1368      *
1369      * <p>Set this value to the number of items this inner GridView will display when it is
1370      * first scrolled into the viewport. RecyclerView will attempt to prefetch that number of items
1371      * so they are ready, avoiding jank as the inner GridView is scrolled into the viewport.</p>
1372      *
1373      * <p>For example, take a VerticalGridView of scrolling HorizontalGridViews. The rows always
1374      * have 6 items visible in them (or 7 if not aligned). Passing <code>6</code> to this method
1375      * for each inner GridView will enable RecyclerView's prefetching feature to do create/bind work
1376      * for 6 views within a row early, before it is scrolled on screen, instead of just the default
1377      * 4.</p>
1378      *
1379      * <p>Calling this method does nothing unless the LayoutManager is in a RecyclerView
1380      * nested in another RecyclerView.</p>
1381      *
1382      * <p class="note"><strong>Note:</strong> Setting this value to be larger than the number of
1383      * views that will be visible in this view can incur unnecessary bind work, and an increase to
1384      * the number of Views created and in active use.</p>
1385      *
1386      * @param itemCount Number of items to prefetch
1387      * @see #getInitialPrefetchItemCount()
1388      * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
1389      * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int,
1390      * RecyclerView.LayoutManager.LayoutPrefetchRegistry)
1391      */
setInitialPrefetchItemCount(int itemCount)1392     public void setInitialPrefetchItemCount(int itemCount) {
1393         mInitialPrefetchItemCount = itemCount;
1394     }
1395 
1396     /**
1397      * Gets the number of items to prefetch in
1398      * {@link RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, RecyclerView.LayoutManager.LayoutPrefetchRegistry)},
1399      * which defines how many inner items should be prefetched when this GridView is nested inside
1400      * another RecyclerView.
1401      *
1402      * @return number of items to prefetch.
1403      * @see RecyclerView.LayoutManager#isItemPrefetchEnabled()
1404      * @see #setInitialPrefetchItemCount(int)
1405      * @see RecyclerView.LayoutManager#collectInitialPrefetchPositions(int,
1406      * RecyclerView.LayoutManager.LayoutPrefetchRegistry)
1407      */
getInitialPrefetchItemCount()1408     public int getInitialPrefetchItemCount() {
1409         return mInitialPrefetchItemCount;
1410     }
1411 
1412     @Override
removeView(@onNull View view)1413     public void removeView(@NonNull View view) {
1414         boolean retainFocusForChild = view.hasFocus() && isFocusable();
1415         if (retainFocusForChild) {
1416             // When animation or scrolling removes a focused child, focus to GridView itself to
1417             // avoid losing focus.
1418             mPrivateFlag |= PFLAG_RETAIN_FOCUS_FOR_CHILD;
1419             requestFocus();
1420         }
1421         super.removeView(view);
1422         if (retainFocusForChild) {
1423             mPrivateFlag ^= ~PFLAG_RETAIN_FOCUS_FOR_CHILD;
1424         }
1425     }
1426 
1427     @Override
removeViewAt(int index)1428     public void removeViewAt(int index) {
1429         boolean retainFocusForChild = getChildAt(index).hasFocus();
1430         if (retainFocusForChild) {
1431             // When animation or scrolling removes a focused child, focus to GridView itself to
1432             // avoid losing focus.
1433             mPrivateFlag |= PFLAG_RETAIN_FOCUS_FOR_CHILD;
1434             requestFocus();
1435         }
1436         super.removeViewAt(index);
1437         if (retainFocusForChild) {
1438             mPrivateFlag ^= ~PFLAG_RETAIN_FOCUS_FOR_CHILD;
1439         }
1440     }
1441 }
1442