ItemAlignmentFacet.java revision bc0edc3ab9bac3c8d7d3cc9de1cb499ea3b4155e
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 * An abstract 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     * Sets 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     * Sets the method for focused item alignment 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     * Returns the method for focused item alignment 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     * Sets 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     * Returns 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     * Sets the 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     * Returns the 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     * Sets 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     * Returns 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     * Sets the 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     * Returns the 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     * Sets 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     * Returns 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     * Sets 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     * Sets 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     * Returns the margin in pixels between two child items vertically.
432     */
433    public int getVerticalMargin() {
434        return mLayoutManager.getVerticalMargin();
435    }
436
437    /**
438     * Sets 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     * Returns the margin in pixels between two child items horizontally.
447     */
448    public int getHorizontalMargin() {
449        return mLayoutManager.getHorizontalMargin();
450    }
451
452    /**
453     * Registers 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     * Registers 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     * Changes the selected item immediately without animation.
476     */
477    public void setSelectedPosition(int position) {
478        mLayoutManager.setSelection(this, position, 0);
479    }
480
481    /**
482     * Changes the selected item immediately without animation, scrollExtra is
483     * applied in primary scroll direction.  The scrollExtra will be kept until
484     * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
485     */
486    public void setSelectedPosition(int position, int scrollExtra) {
487        mLayoutManager.setSelection(this, position, scrollExtra);
488    }
489
490    /**
491     * Changes the selected item and run an animation to scroll to the target
492     * position.
493     */
494    public void setSelectedPositionSmooth(int position) {
495        mLayoutManager.setSelectionSmooth(this, position);
496    }
497
498    /**
499     * Returns the selected item position.
500     */
501    public int getSelectedPosition() {
502        return mLayoutManager.getSelection();
503    }
504
505    /**
506     * Sets whether an animation should run when a child changes size or when adding
507     * or removing a child.
508     * <p><i>Unstable API, might change later.</i>
509     */
510    public void setAnimateChildLayout(boolean animateChildLayout) {
511        if (mAnimateChildLayout != animateChildLayout) {
512            mAnimateChildLayout = animateChildLayout;
513            if (!mAnimateChildLayout) {
514                mSavedItemAnimator = getItemAnimator();
515                super.setItemAnimator(null);
516            } else {
517                super.setItemAnimator(mSavedItemAnimator);
518            }
519        }
520    }
521
522    /**
523     * Returns true if an animation will run when a child changes size or when
524     * adding or removing a child.
525     * <p><i>Unstable API, might change later.</i>
526     */
527    public boolean isChildLayoutAnimated() {
528        return mAnimateChildLayout;
529    }
530
531    /**
532     * Sets the gravity used for child view positioning. Defaults to
533     * GRAVITY_TOP|GRAVITY_START.
534     *
535     * @param gravity See {@link android.view.Gravity}
536     */
537    public void setGravity(int gravity) {
538        mLayoutManager.setGravity(gravity);
539        requestLayout();
540    }
541
542    @Override
543    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
544        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
545                previouslyFocusedRect);
546    }
547
548    /**
549     * Returns the x/y offsets to final position from current position if the view
550     * is selected.
551     *
552     * @param view The view to get offsets.
553     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
554     */
555    public void getViewSelectedOffsets(View view, int[] offsets) {
556        mLayoutManager.getViewSelectedOffsets(view, offsets);
557    }
558
559    @Override
560    public int getChildDrawingOrder(int childCount, int i) {
561        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
562    }
563
564    final boolean isChildrenDrawingOrderEnabledInternal() {
565        return isChildrenDrawingOrderEnabled();
566    }
567
568    /**
569     * Disables or enables focus search.
570     */
571    public final void setFocusSearchDisabled(boolean disabled) {
572        // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
573        // re-gain focus after a BACK key pressed, so block children focus during transition.
574        setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
575        mLayoutManager.setFocusSearchDisabled(disabled);
576    }
577
578    /**
579     * Returns true if focus search is disabled.
580     */
581    public final boolean isFocusSearchDisabled() {
582        return mLayoutManager.isFocusSearchDisabled();
583    }
584
585    /**
586     * Enables or disables layout.  All children will be removed when layout is
587     * disabled.
588     */
589    public void setLayoutEnabled(boolean layoutEnabled) {
590        mLayoutManager.setLayoutEnabled(layoutEnabled);
591    }
592
593    /**
594     * Changes and overrides children's visibility.
595     */
596    public void setChildrenVisibility(int visibility) {
597        mLayoutManager.setChildrenVisibility(visibility);
598    }
599
600    /**
601     * Enables or disables pruning of children.  Disable is useful during transition.
602     */
603    public void setPruneChild(boolean pruneChild) {
604        mLayoutManager.setPruneChild(pruneChild);
605    }
606
607    /**
608     * Enables or disables scrolling.  Disable is useful during transition.
609     */
610    public void setScrollEnabled(boolean scrollEnabled) {
611        mLayoutManager.setScrollEnabled(scrollEnabled);
612    }
613
614    /**
615     * Returns true if scrolling is enabled.
616     */
617    public boolean isScrollEnabled() {
618        return mLayoutManager.isScrollEnabled();
619    }
620
621    /**
622     * Returns true if the view at the given position has a same row sibling
623     * in front of it.  This will return true if first item view is not created.
624     * So application should check in both {@link OnChildSelectedListener} and {@link
625     * OnChildLaidOutListener}.
626     *
627     * @param position Position in adapter.
628     */
629    public boolean hasPreviousViewInSameRow(int position) {
630        return mLayoutManager.hasPreviousViewInSameRow(position);
631    }
632
633    /**
634     * Enables or disables the default "focus draw at last" order rule.
635     */
636    public void setFocusDrawingOrderEnabled(boolean enabled) {
637        super.setChildrenDrawingOrderEnabled(enabled);
638    }
639
640    /**
641     * Returns true if default "focus draw at last" order rule is enabled.
642     */
643    public boolean isFocusDrawingOrderEnabled() {
644        return super.isChildrenDrawingOrderEnabled();
645    }
646
647    /**
648     * Sets the touch intercept listener.
649     */
650    public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
651        mOnTouchInterceptListener = listener;
652    }
653
654    /**
655     * Sets the generic motion intercept listener.
656     */
657    public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
658        mOnMotionInterceptListener = listener;
659    }
660
661    /**
662     * Sets the key intercept listener.
663     */
664    public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
665        mOnKeyInterceptListener = listener;
666    }
667
668    /**
669     * Sets the unhandled key listener.
670     */
671    public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
672        mOnUnhandledKeyListener = listener;
673    }
674
675    /**
676     * Returns the unhandled key listener.
677     */
678    public OnUnhandledKeyListener getOnUnhandledKeyListener() {
679        return mOnUnhandledKeyListener;
680    }
681
682    @Override
683    public boolean dispatchKeyEvent(KeyEvent event) {
684        if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
685            return true;
686        }
687        if (super.dispatchKeyEvent(event)) {
688            return true;
689        }
690        if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) {
691            return true;
692        }
693        return false;
694    }
695
696    @Override
697    public boolean dispatchTouchEvent(MotionEvent event) {
698        if (mOnTouchInterceptListener != null) {
699            if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
700                return true;
701            }
702        }
703        return super.dispatchTouchEvent(event);
704    }
705
706    @Override
707    public boolean dispatchGenericFocusedEvent(MotionEvent event) {
708        if (mOnMotionInterceptListener != null) {
709            if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
710                return true;
711            }
712        }
713        return super.dispatchGenericFocusedEvent(event);
714    }
715
716    /**
717     * Returns the policy for saving children.
718     *
719     * @return policy, one of {@link #SAVE_NO_CHILD}
720     * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
721     */
722    public final int getSaveChildrenPolicy() {
723        return mLayoutManager.mChildrenStates.getSavePolicy();
724    }
725
726    /**
727     * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
728     *         {@link #SAVE_LIMITED_CHILD}
729     */
730    public final int getSaveChildrenLimitNumber() {
731        return mLayoutManager.mChildrenStates.getLimitNumber();
732    }
733
734    /**
735     * Sets the policy for saving children.
736     * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
737     * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
738     */
739    public final void setSaveChildrenPolicy(int savePolicy) {
740        mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
741    }
742
743    /**
744     * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
745     */
746    public final void setSaveChildrenLimitNumber(int limitNumber) {
747        mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
748    }
749
750    @Override
751    public boolean hasOverlappingRendering() {
752        return mHasOverlappingRendering;
753    }
754
755    public void setHasOverlappingRendering(boolean hasOverlapping) {
756        mHasOverlappingRendering = hasOverlapping;
757    }
758
759    /**
760     * Notify layout manager that layout directionality has been updated
761     */
762    @Override
763    public void onRtlPropertiesChanged(int layoutDirection) {
764        mLayoutManager.onRtlPropertiesChanged(layoutDirection);
765    }
766
767    @Override
768    public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
769        mChainedRecyclerListener = listener;
770    }
771}
772