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