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