BaseGridView.java revision 15375aa6fd54b036f97f99229aefab2822c8a1c9
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) {
581                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
582                    @Override
583                    public void onChildViewHolderSelected(RecyclerView parent,
584                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
585                        if (selectedPosition == position) {
586                            removeOnChildViewHolderSelectedListener(this);
587                            task.run(child);
588                        }
589                    }
590                });
591            } else {
592                task.run(vh);
593            }
594        }
595        setSelectedPositionSmooth(position);
596    }
597
598    /**
599     * Perform a task on ViewHolder at given position after scroll to it.
600     * @param position Position of item in adapter.
601     * @param task Task to executed on the ViewHolder at a given position.
602     */
603    public void setSelectedPosition(final int position, final ViewHolderTask task) {
604        if (task != null) {
605            RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
606            if (vh == null) {
607                addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
608                    @Override
609                    public void onChildViewHolderSelected(RecyclerView parent,
610                            RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
611                        if (selectedPosition == position) {
612                            removeOnChildViewHolderSelectedListener(this);
613                            task.run(child);
614                        }
615                    }
616                });
617            } else {
618                task.run(vh);
619            }
620        }
621        setSelectedPosition(position);
622    }
623
624    /**
625     * Returns the selected item position.
626     */
627    public int getSelectedPosition() {
628        return mLayoutManager.getSelection();
629    }
630
631    /**
632     * Returns the sub selected item position started from zero.  An item can have
633     * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
634     * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
635     * is defined.
636     */
637    public int getSelectedSubPosition() {
638        return mLayoutManager.getSubSelection();
639    }
640
641    /**
642     * Sets whether an animation should run when a child changes size or when adding
643     * or removing a child.
644     * <p><i>Unstable API, might change later.</i>
645     */
646    public void setAnimateChildLayout(boolean animateChildLayout) {
647        if (mAnimateChildLayout != animateChildLayout) {
648            mAnimateChildLayout = animateChildLayout;
649            if (!mAnimateChildLayout) {
650                mSavedItemAnimator = getItemAnimator();
651                super.setItemAnimator(null);
652            } else {
653                super.setItemAnimator(mSavedItemAnimator);
654            }
655        }
656    }
657
658    /**
659     * Returns true if an animation will run when a child changes size or when
660     * adding or removing a child.
661     * <p><i>Unstable API, might change later.</i>
662     */
663    public boolean isChildLayoutAnimated() {
664        return mAnimateChildLayout;
665    }
666
667    /**
668     * Sets the gravity used for child view positioning. Defaults to
669     * GRAVITY_TOP|GRAVITY_START.
670     *
671     * @param gravity See {@link android.view.Gravity}
672     */
673    public void setGravity(int gravity) {
674        mLayoutManager.setGravity(gravity);
675        requestLayout();
676    }
677
678    @Override
679    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
680        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
681                previouslyFocusedRect);
682    }
683
684    /**
685     * Returns the x/y offsets to final position from current position if the view
686     * is selected.
687     *
688     * @param view The view to get offsets.
689     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
690     */
691    public void getViewSelectedOffsets(View view, int[] offsets) {
692        mLayoutManager.getViewSelectedOffsets(view, offsets);
693    }
694
695    @Override
696    public int getChildDrawingOrder(int childCount, int i) {
697        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
698    }
699
700    final boolean isChildrenDrawingOrderEnabledInternal() {
701        return isChildrenDrawingOrderEnabled();
702    }
703
704    @Override
705    public View focusSearch(int direction) {
706        if (isFocused()) {
707            // focusSearch(int) is called when GridView itself is focused.
708            // Calling focusSearch(view, int) to get next sibling of current selected child.
709            View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
710            if (view != null) {
711                return focusSearch(view, direction);
712            }
713        }
714        // otherwise, go to mParent to perform focusSearch
715        return super.focusSearch(direction);
716    }
717
718    @Override
719    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
720        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
721        mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
722    }
723
724    /**
725     * Disables or enables focus search.
726     */
727    public final void setFocusSearchDisabled(boolean disabled) {
728        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
729        // re-gain focus after a BACK key pressed, so block children focus during transition.
730        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
731        mLayoutManager.setFocusSearchDisabled(disabled);
732    }
733
734    /**
735     * Returns true if focus search is disabled.
736     */
737    public final boolean isFocusSearchDisabled() {
738        return mLayoutManager.isFocusSearchDisabled();
739    }
740
741    /**
742     * Enables or disables layout.  All children will be removed when layout is
743     * disabled.
744     */
745    public void setLayoutEnabled(boolean layoutEnabled) {
746        mLayoutManager.setLayoutEnabled(layoutEnabled);
747    }
748
749    /**
750     * Changes and overrides children's visibility.
751     */
752    public void setChildrenVisibility(int visibility) {
753        mLayoutManager.setChildrenVisibility(visibility);
754    }
755
756    /**
757     * Enables or disables pruning of children.  Disable is useful during transition.
758     */
759    public void setPruneChild(boolean pruneChild) {
760        mLayoutManager.setPruneChild(pruneChild);
761    }
762
763    /**
764     * Enables or disables scrolling.  Disable is useful during transition.
765     */
766    public void setScrollEnabled(boolean scrollEnabled) {
767        mLayoutManager.setScrollEnabled(scrollEnabled);
768    }
769
770    /**
771     * Returns true if scrolling is enabled.
772     */
773    public boolean isScrollEnabled() {
774        return mLayoutManager.isScrollEnabled();
775    }
776
777    /**
778     * Returns true if the view at the given position has a same row sibling
779     * in front of it.  This will return true if first item view is not created.
780     * So application should check in both {@link OnChildSelectedListener} and {@link
781     * OnChildLaidOutListener}.
782     *
783     * @param position Position in adapter.
784     */
785    public boolean hasPreviousViewInSameRow(int position) {
786        return mLayoutManager.hasPreviousViewInSameRow(position);
787    }
788
789    /**
790     * Enables or disables the default "focus draw at last" order rule.
791     */
792    public void setFocusDrawingOrderEnabled(boolean enabled) {
793        super.setChildrenDrawingOrderEnabled(enabled);
794    }
795
796    /**
797     * Returns true if default "focus draw at last" order rule is enabled.
798     */
799    public boolean isFocusDrawingOrderEnabled() {
800        return super.isChildrenDrawingOrderEnabled();
801    }
802
803    /**
804     * Sets the touch intercept listener.
805     */
806    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
807        mOnTouchInterceptListener = listener;
808    }
809
810    /**
811     * Sets the generic motion intercept listener.
812     */
813    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
814        mOnMotionInterceptListener = listener;
815    }
816
817    /**
818     * Sets the key intercept listener.
819     */
820    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
821        mOnKeyInterceptListener = listener;
822    }
823
824    /**
825     * Sets the unhandled key listener.
826     */
827    public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
828        mOnUnhandledKeyListener = listener;
829    }
830
831    /**
832     * Returns the unhandled key listener.
833     */
834    public OnUnhandledKeyListener getOnUnhandledKeyListener() {
835        return mOnUnhandledKeyListener;
836    }
837
838    @Override
839    public boolean dispatchKeyEvent(KeyEvent event) {
840        if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
841            return true;
842        }
843        if (super.dispatchKeyEvent(event)) {
844            return true;
845        }
846        if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) {
847            return true;
848        }
849        return false;
850    }
851
852    @Override
853    public boolean dispatchTouchEvent(MotionEvent event) {
854        if (mOnTouchInterceptListener != null) {
855            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
856                return true;
857            }
858        }
859        return super.dispatchTouchEvent(event);
860    }
861
862    @Override
863    public boolean dispatchGenericFocusedEvent(MotionEvent event) {
864        if (mOnMotionInterceptListener != null) {
865            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
866                return true;
867            }
868        }
869        return super.dispatchGenericFocusedEvent(event);
870    }
871
872    /**
873     * Returns the policy for saving children.
874     *
875     * @return policy, one of {@link #SAVE_NO_CHILD}
876     * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
877     */
878    public final int getSaveChildrenPolicy() {
879        return mLayoutManager.mChildrenStates.getSavePolicy();
880    }
881
882    /**
883     * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
884     *         {@link #SAVE_LIMITED_CHILD}
885     */
886    public final int getSaveChildrenLimitNumber() {
887        return mLayoutManager.mChildrenStates.getLimitNumber();
888    }
889
890    /**
891     * Sets the policy for saving children.
892     * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
893     * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
894     */
895    public final void setSaveChildrenPolicy(int savePolicy) {
896        mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
897    }
898
899    /**
900     * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
901     */
902    public final void setSaveChildrenLimitNumber(int limitNumber) {
903        mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
904    }
905
906    @Override
907    public boolean hasOverlappingRendering() {
908        return mHasOverlappingRendering;
909    }
910
911    public void setHasOverlappingRendering(boolean hasOverlapping) {
912        mHasOverlappingRendering = hasOverlapping;
913    }
914
915    /**
916     * Notify layout manager that layout directionality has been updated
917     */
918    @Override
919    public void onRtlPropertiesChanged(int layoutDirection) {
920        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
921    }
922
923    @Override
924    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
925        mChainedRecyclerListener = listener;
926    }
927
928    /**
929     * Sets pixels of extra space for layout child in invisible area.
930     *
931     * @param extraLayoutSpace  Pixels of extra space for layout invisible child.
932     *                          Must be bigger or equals to 0.
933     * @hide
934     */
935    public void setExtraLayoutSpace(int extraLayoutSpace) {
936        mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
937    }
938
939    /**
940     * Returns pixels of extra space for layout child in invisible area.
941     *
942     * @hide
943     */
944    public int getExtraLayoutSpace() {
945        return mLayoutManager.getExtraLayoutSpace();
946    }
947
948}
949