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