BaseGridView.java revision c39d9c75590eca86a5e7e32a8824ba04a0d42e9b
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 android.content.Context;
17import android.content.res.TypedArray;
18import android.graphics.Rect;
19import android.support.annotation.RestrictTo;
20import android.support.v17.leanback.R;
21import android.support.v7.widget.RecyclerView;
22import android.util.AttributeSet;
23import android.view.Gravity;
24import android.view.KeyEvent;
25import android.view.MotionEvent;
26import android.view.View;
27import android.support.v7.widget.SimpleItemAnimator;
28
29import static android.support.annotation.RestrictTo.Scope.GROUP_ID;
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(GROUP_ID)
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    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
198        super(context, attrs, defStyle);
199        mLayoutManager = new GridLayoutManager(this);
200        setLayoutManager(mLayoutManager);
201        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
202        setHasFixedSize(true);
203        setChildrenDrawingOrderEnabled(true);
204        setWillNotDraw(true);
205        setOverScrollMode(View.OVER_SCROLL_NEVER);
206        // Disable change animation by default on leanback.
207        // Change animation will create a new view and cause undesired
208        // focus animation between the old view and new view.
209        ((SimpleItemAnimator)getItemAnimator()).setSupportsChangeAnimations(false);
210        super.setRecyclerListener(new RecyclerView.RecyclerListener() {
211            @Override
212            public void onViewRecycled(RecyclerView.ViewHolder holder) {
213                mLayoutManager.onChildRecycled(holder);
214                if (mChainedRecyclerListener != null) {
215                    mChainedRecyclerListener.onViewRecycled(holder);
216                }
217            }
218        });
219    }
220
221    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
222        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
223        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
224        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
225        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
226        boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true);
227        boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true);
228        mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd);
229        mLayoutManager.setVerticalMargin(
230                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
231        mLayoutManager.setHorizontalMargin(
232                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
233        if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
234            setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
235        }
236        a.recycle();
237    }
238
239    /**
240     * Sets the strategy used to scroll in response to item focus changing:
241     * <ul>
242     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
243     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
244     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
245     * </ul>
246     */
247    public void setFocusScrollStrategy(int scrollStrategy) {
248        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
249            && scrollStrategy != FOCUS_SCROLL_PAGE) {
250            throw new IllegalArgumentException("Invalid scrollStrategy");
251        }
252        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
253        requestLayout();
254    }
255
256    /**
257     * Returns the strategy used to scroll in response to item focus changing.
258     * <ul>
259     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
260     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
261     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
262     * </ul>
263     */
264    public int getFocusScrollStrategy() {
265        return mLayoutManager.getFocusScrollStrategy();
266    }
267
268    /**
269     * Sets the method for focused item alignment in the view.
270     *
271     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
272     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
273     *        {@link #WINDOW_ALIGN_NO_EDGE}.
274     */
275    public void setWindowAlignment(int windowAlignment) {
276        mLayoutManager.setWindowAlignment(windowAlignment);
277        requestLayout();
278    }
279
280    /**
281     * Returns the method for focused item alignment in the view.
282     *
283     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
284     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
285     */
286    public int getWindowAlignment() {
287        return mLayoutManager.getWindowAlignment();
288    }
289
290    /**
291     * Sets the offset in pixels for window alignment.
292     *
293     * @param offset The number of pixels to offset.  If the offset is positive,
294     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
295     *        if the offset is negative, the absolute value is distance from high
296     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
297     *        Default value is 0.
298     */
299    public void setWindowAlignmentOffset(int offset) {
300        mLayoutManager.setWindowAlignmentOffset(offset);
301        requestLayout();
302    }
303
304    /**
305     * Returns the offset in pixels for window alignment.
306     *
307     * @return The number of pixels to offset.  If the offset is positive,
308     *        it is distance from low edge (see {@link #WINDOW_ALIGN_LOW_EDGE});
309     *        if the offset is negative, the absolute value is distance from high
310     *        edge (see {@link #WINDOW_ALIGN_HIGH_EDGE}).
311     *        Default value is 0.
312     */
313    public int getWindowAlignmentOffset() {
314        return mLayoutManager.getWindowAlignmentOffset();
315    }
316
317    /**
318     * Sets the offset percent for window alignment in addition to {@link
319     * #getWindowAlignmentOffset()}.
320     *
321     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
322     *        width from low edge. Use
323     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
324     *         Default value is 50.
325     */
326    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
327        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
328        requestLayout();
329    }
330
331    /**
332     * Returns the offset percent for window alignment in addition to
333     * {@link #getWindowAlignmentOffset()}.
334     *
335     * @return Percentage to offset. E.g., 40 means 40% of the width from the
336     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
337     *         disabled. Default value is 50.
338     */
339    public float getWindowAlignmentOffsetPercent() {
340        return mLayoutManager.getWindowAlignmentOffsetPercent();
341    }
342
343    /**
344     * Sets the absolute offset in pixels for item alignment.
345     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
346     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
347     *
348     * @param offset The number of pixels to offset. Can be negative for
349     *        alignment from the high edge, or positive for alignment from the
350     *        low edge.
351     */
352    public void setItemAlignmentOffset(int offset) {
353        mLayoutManager.setItemAlignmentOffset(offset);
354        requestLayout();
355    }
356
357    /**
358     * Returns the absolute offset in pixels for item alignment.
359     *
360     * @return The number of pixels to offset. Will be negative for alignment
361     *         from the high edge, or positive for alignment from the low edge.
362     *         Default value is 0.
363     */
364    public int getItemAlignmentOffset() {
365        return mLayoutManager.getItemAlignmentOffset();
366    }
367
368    /**
369     * Set to true if include padding in calculating item align offset.
370     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
371     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
372     *
373     * @param withPadding When it is true: we include left/top padding for positive
374     *          item offset, include right/bottom padding for negative item offset.
375     */
376    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
377        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
378        requestLayout();
379    }
380
381    /**
382     * Returns true if include padding in calculating item align offset.
383     */
384    public boolean isItemAlignmentOffsetWithPadding() {
385        return mLayoutManager.isItemAlignmentOffsetWithPadding();
386    }
387
388    /**
389     * Sets the offset percent for item alignment in addition to {@link
390     * #getItemAlignmentOffset()}.
391     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
392     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
393     *
394     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
395     *        width from the low edge. Use
396     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
397     */
398    public void setItemAlignmentOffsetPercent(float offsetPercent) {
399        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
400        requestLayout();
401    }
402
403    /**
404     * Returns the offset percent for item alignment in addition to {@link
405     * #getItemAlignmentOffset()}.
406     *
407     * @return Percentage to offset. E.g., 40 means 40% of the width from the
408     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
409     *         disabled. Default value is 50.
410     */
411    public float getItemAlignmentOffsetPercent() {
412        return mLayoutManager.getItemAlignmentOffsetPercent();
413    }
414
415    /**
416     * Sets the id of the view to align with. Use {@link android.view.View#NO_ID} (default)
417     * for the item view itself.
418     * Item alignment settings are ignored for the child if {@link ItemAlignmentFacet}
419     * is provided by {@link RecyclerView.ViewHolder} or {@link FacetProviderAdapter}.
420     */
421    public void setItemAlignmentViewId(int viewId) {
422        mLayoutManager.setItemAlignmentViewId(viewId);
423    }
424
425    /**
426     * Returns the id of the view to align with, or zero for the item view itself.
427     */
428    public int getItemAlignmentViewId() {
429        return mLayoutManager.getItemAlignmentViewId();
430    }
431
432    /**
433     * Sets the margin in pixels between two child items.
434     */
435    public void setItemMargin(int margin) {
436        mLayoutManager.setItemMargin(margin);
437        requestLayout();
438    }
439
440    /**
441     * Sets the margin in pixels between two child items vertically.
442     */
443    public void setVerticalMargin(int margin) {
444        mLayoutManager.setVerticalMargin(margin);
445        requestLayout();
446    }
447
448    /**
449     * Returns the margin in pixels between two child items vertically.
450     */
451    public int getVerticalMargin() {
452        return mLayoutManager.getVerticalMargin();
453    }
454
455    /**
456     * Sets the margin in pixels between two child items horizontally.
457     */
458    public void setHorizontalMargin(int margin) {
459        mLayoutManager.setHorizontalMargin(margin);
460        requestLayout();
461    }
462
463    /**
464     * Returns the margin in pixels between two child items horizontally.
465     */
466    public int getHorizontalMargin() {
467        return mLayoutManager.getHorizontalMargin();
468    }
469
470    /**
471     * Registers a callback to be invoked when an item in BaseGridView has
472     * been laid out.
473     *
474     * @param listener The listener to be invoked.
475     */
476    public void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
477        mLayoutManager.setOnChildLaidOutListener(listener);
478    }
479
480    /**
481     * Registers a callback to be invoked when an item in BaseGridView has
482     * been selected.  Note that the listener may be invoked when there is a
483     * layout pending on the view, affording the listener an opportunity to
484     * adjust the upcoming layout based on the selection state.
485     *
486     * @param listener The listener to be invoked.
487     */
488    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
489        mLayoutManager.setOnChildSelectedListener(listener);
490    }
491
492    /**
493     * Registers a callback to be invoked when an item in BaseGridView has
494     * been selected.  Note that the listener may be invoked when there is a
495     * layout pending on the view, affording the listener an opportunity to
496     * adjust the upcoming layout based on the selection state.
497     * This method will clear all existing listeners added by
498     * {@link #addOnChildViewHolderSelectedListener}.
499     *
500     * @param listener The listener to be invoked.
501     */
502    public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
503        mLayoutManager.setOnChildViewHolderSelectedListener(listener);
504    }
505
506    /**
507     * Registers a callback to be invoked when an item in BaseGridView has
508     * been selected.  Note that the listener may be invoked when there is a
509     * layout pending on the view, affording the listener an opportunity to
510     * adjust the upcoming layout based on the selection state.
511     *
512     * @param listener The listener to be invoked.
513     */
514    public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
515        mLayoutManager.addOnChildViewHolderSelectedListener(listener);
516    }
517
518    /**
519     * Remove the callback invoked when an item in BaseGridView has been selected.
520     *
521     * @param listener The listener to be removed.
522     */
523    public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)
524            {
525        mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
526    }
527
528    /**
529     * Changes the selected item immediately without animation.
530     */
531    public void setSelectedPosition(int position) {
532        mLayoutManager.setSelection(position, 0);
533    }
534
535    /**
536     * Changes the selected item and/or subposition immediately without animation.
537     */
538    public void setSelectedPositionWithSub(int position, int subposition) {
539        mLayoutManager.setSelectionWithSub(position, subposition, 0);
540    }
541
542    /**
543     * Changes the selected item immediately without animation, scrollExtra is
544     * applied in primary scroll direction.  The scrollExtra will be kept until
545     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
546     */
547    public void setSelectedPosition(int position, int scrollExtra) {
548        mLayoutManager.setSelection(position, scrollExtra);
549    }
550
551    /**
552     * Changes the selected item and/or subposition immediately without animation, scrollExtra is
553     * applied in primary scroll direction.  The scrollExtra will be kept until
554     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
555     */
556    public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
557        mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
558    }
559
560    /**
561     * Changes the selected item and run an animation to scroll to the target
562     * position.
563     */
564    public void setSelectedPositionSmooth(int position) {
565        mLayoutManager.setSelectionSmooth(position);
566    }
567
568    /**
569     * Changes the selected item and/or subposition, runs an animation to scroll to the target
570     * position.
571     */
572    public void setSelectedPositionSmoothWithSub(int position, int subposition) {
573        mLayoutManager.setSelectionSmoothWithSub(position, subposition);
574    }
575
576    /**
577     * Perform a task on ViewHolder at given position after smooth scrolling to it.
578     * @param position Position of item in adapter.
579     * @param task Task to executed on the ViewHolder at a given position.
580     */
581    public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) {
582        if (task != null) {
583            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
584            if (vh == null || hasPendingAdapterUpdates()) {
585                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
586                    @Override
587                    public void onChildViewHolderSelected(RecyclerView parent,
588                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
589                        if (selectedPosition == position) {
590                            removeOnChildViewHolderSelectedListener(this);
591                            task.run(child);
592                        }
593                    }
594                });
595            } else {
596                task.run(vh);
597            }
598        }
599        setSelectedPositionSmooth(position);
600    }
601
602    /**
603     * Perform a task on ViewHolder at given position after scroll to it.
604     * @param position Position of item in adapter.
605     * @param task Task to executed on the ViewHolder at a given position.
606     */
607    public void setSelectedPosition(final int position, final ViewHolderTask task) {
608        if (task != null) {
609            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
610            if (vh == null || hasPendingAdapterUpdates()) {
611                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
612                    @Override
613                    public void onChildViewHolderSelected(RecyclerView parent,
614                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
615                        if (selectedPosition == position) {
616                            removeOnChildViewHolderSelectedListener(this);
617                            task.run(child);
618                        }
619                    }
620                });
621            } else {
622                task.run(vh);
623            }
624        }
625        setSelectedPosition(position);
626    }
627
628    /**
629     * Returns the selected item position.
630     */
631    public int getSelectedPosition() {
632        return mLayoutManager.getSelection();
633    }
634
635    /**
636     * Returns the sub selected item position started from zero.  An item can have
637     * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
638     * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
639     * is defined.
640     */
641    public int getSelectedSubPosition() {
642        return mLayoutManager.getSubSelection();
643    }
644
645    /**
646     * Sets whether an animation should run when a child changes size or when adding
647     * or removing a child.
648     * <p><i>Unstable API, might change later.</i>
649     */
650    public void setAnimateChildLayout(boolean animateChildLayout) {
651        if (mAnimateChildLayout != animateChildLayout) {
652            mAnimateChildLayout = animateChildLayout;
653            if (!mAnimateChildLayout) {
654                mSavedItemAnimator = getItemAnimator();
655                super.setItemAnimator(null);
656            } else {
657                super.setItemAnimator(mSavedItemAnimator);
658            }
659        }
660    }
661
662    /**
663     * Returns true if an animation will run when a child changes size or when
664     * adding or removing a child.
665     * <p><i>Unstable API, might change later.</i>
666     */
667    public boolean isChildLayoutAnimated() {
668        return mAnimateChildLayout;
669    }
670
671    /**
672     * Sets the gravity used for child view positioning. Defaults to
673     * GRAVITY_TOP|GRAVITY_START.
674     *
675     * @param gravity See {@link android.view.Gravity}
676     */
677    public void setGravity(int gravity) {
678        mLayoutManager.setGravity(gravity);
679        requestLayout();
680    }
681
682    @Override
683    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
684        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
685                previouslyFocusedRect);
686    }
687
688    /**
689     * Returns the x/y offsets to final position from current position if the view
690     * is selected.
691     *
692     * @param view The view to get offsets.
693     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
694     */
695    public void getViewSelectedOffsets(View view, int[] offsets) {
696        mLayoutManager.getViewSelectedOffsets(view, offsets);
697    }
698
699    @Override
700    public int getChildDrawingOrder(int childCount, int i) {
701        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
702    }
703
704    final boolean isChildrenDrawingOrderEnabledInternal() {
705        return isChildrenDrawingOrderEnabled();
706    }
707
708    @Override
709    public View focusSearch(int direction) {
710        if (isFocused()) {
711            // focusSearch(int) is called when GridView itself is focused.
712            // Calling focusSearch(view, int) to get next sibling of current selected child.
713            View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
714            if (view != null) {
715                return focusSearch(view, direction);
716            }
717        }
718        // otherwise, go to mParent to perform focusSearch
719        return super.focusSearch(direction);
720    }
721
722    @Override
723    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
724        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
725        mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
726    }
727
728    /**
729     * Disables or enables focus search.
730     */
731    public final void setFocusSearchDisabled(boolean disabled) {
732        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
733        // re-gain focus after a BACK key pressed, so block children focus during transition.
734        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
735        mLayoutManager.setFocusSearchDisabled(disabled);
736    }
737
738    /**
739     * Returns true if focus search is disabled.
740     */
741    public final boolean isFocusSearchDisabled() {
742        return mLayoutManager.isFocusSearchDisabled();
743    }
744
745    /**
746     * Enables or disables layout.  All children will be removed when layout is
747     * disabled.
748     */
749    public void setLayoutEnabled(boolean layoutEnabled) {
750        mLayoutManager.setLayoutEnabled(layoutEnabled);
751    }
752
753    /**
754     * Changes and overrides children's visibility.
755     */
756    public void setChildrenVisibility(int visibility) {
757        mLayoutManager.setChildrenVisibility(visibility);
758    }
759
760    /**
761     * Enables or disables pruning of children.  Disable is useful during transition.
762     */
763    public void setPruneChild(boolean pruneChild) {
764        mLayoutManager.setPruneChild(pruneChild);
765    }
766
767    /**
768     * Enables or disables scrolling.  Disable is useful during transition.
769     */
770    public void setScrollEnabled(boolean scrollEnabled) {
771        mLayoutManager.setScrollEnabled(scrollEnabled);
772    }
773
774    /**
775     * Returns true if scrolling is enabled.
776     */
777    public boolean isScrollEnabled() {
778        return mLayoutManager.isScrollEnabled();
779    }
780
781    /**
782     * Returns true if the view at the given position has a same row sibling
783     * in front of it.  This will return true if first item view is not created.
784     * So application should check in both {@link OnChildSelectedListener} and {@link
785     * OnChildLaidOutListener}.
786     *
787     * @param position Position in adapter.
788     */
789    public boolean hasPreviousViewInSameRow(int position) {
790        return mLayoutManager.hasPreviousViewInSameRow(position);
791    }
792
793    /**
794     * Enables or disables the default "focus draw at last" order rule.
795     */
796    public void setFocusDrawingOrderEnabled(boolean enabled) {
797        super.setChildrenDrawingOrderEnabled(enabled);
798    }
799
800    /**
801     * Returns true if default "focus draw at last" order rule is enabled.
802     */
803    public boolean isFocusDrawingOrderEnabled() {
804        return super.isChildrenDrawingOrderEnabled();
805    }
806
807    /**
808     * Sets the touch intercept listener.
809     */
810    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
811        mOnTouchInterceptListener = listener;
812    }
813
814    /**
815     * Sets the generic motion intercept listener.
816     */
817    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
818        mOnMotionInterceptListener = listener;
819    }
820
821    /**
822     * Sets the key intercept listener.
823     */
824    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
825        mOnKeyInterceptListener = listener;
826    }
827
828    /**
829     * Sets the unhandled key listener.
830     */
831    public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
832        mOnUnhandledKeyListener = listener;
833    }
834
835    /**
836     * Returns the unhandled key listener.
837     */
838    public OnUnhandledKeyListener getOnUnhandledKeyListener() {
839        return mOnUnhandledKeyListener;
840    }
841
842    @Override
843    public boolean dispatchKeyEvent(KeyEvent event) {
844        if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
845            return true;
846        }
847        if (super.dispatchKeyEvent(event)) {
848            return true;
849        }
850        if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) {
851            return true;
852        }
853        return false;
854    }
855
856    @Override
857    public boolean dispatchTouchEvent(MotionEvent event) {
858        if (mOnTouchInterceptListener != null) {
859            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
860                return true;
861            }
862        }
863        return super.dispatchTouchEvent(event);
864    }
865
866    @Override
867    public boolean dispatchGenericFocusedEvent(MotionEvent event) {
868        if (mOnMotionInterceptListener != null) {
869            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
870                return true;
871            }
872        }
873        return super.dispatchGenericFocusedEvent(event);
874    }
875
876    /**
877     * Returns the policy for saving children.
878     *
879     * @return policy, one of {@link #SAVE_NO_CHILD}
880     * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
881     */
882    public final int getSaveChildrenPolicy() {
883        return mLayoutManager.mChildrenStates.getSavePolicy();
884    }
885
886    /**
887     * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
888     *         {@link #SAVE_LIMITED_CHILD}
889     */
890    public final int getSaveChildrenLimitNumber() {
891        return mLayoutManager.mChildrenStates.getLimitNumber();
892    }
893
894    /**
895     * Sets the policy for saving children.
896     * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
897     * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
898     */
899    public final void setSaveChildrenPolicy(int savePolicy) {
900        mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
901    }
902
903    /**
904     * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
905     */
906    public final void setSaveChildrenLimitNumber(int limitNumber) {
907        mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
908    }
909
910    @Override
911    public boolean hasOverlappingRendering() {
912        return mHasOverlappingRendering;
913    }
914
915    public void setHasOverlappingRendering(boolean hasOverlapping) {
916        mHasOverlappingRendering = hasOverlapping;
917    }
918
919    /**
920     * Notify layout manager that layout directionality has been updated
921     */
922    @Override
923    public void onRtlPropertiesChanged(int layoutDirection) {
924        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
925    }
926
927    @Override
928    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
929        mChainedRecyclerListener = listener;
930    }
931
932    /**
933     * Sets pixels of extra space for layout child in invisible area.
934     *
935     * @param extraLayoutSpace  Pixels of extra space for layout invisible child.
936     *                          Must be bigger or equals to 0.
937     * @hide
938     */
939    @RestrictTo(GROUP_ID)
940    public void setExtraLayoutSpace(int extraLayoutSpace) {
941        mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
942    }
943
944    /**
945     * Returns pixels of extra space for layout child in invisible area.
946     *
947     * @hide
948     */
949    @RestrictTo(GROUP_ID)
950    public int getExtraLayoutSpace() {
951        return mLayoutManager.getExtraLayoutSpace();
952    }
953
954}
955