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