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