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