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  */
14 package android.support.v17.leanback.widget;
15 
16 import android.content.Context;
17 import android.content.res.TypedArray;
18 import android.graphics.Rect;
19 import android.support.v17.leanback.R;
20 import android.support.v7.widget.RecyclerView;
21 import android.util.AttributeSet;
22 import android.view.Gravity;
23 import android.view.KeyEvent;
24 import android.view.MotionEvent;
25 import android.view.View;
26 import 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  */
34 abstract 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          */
onInterceptTouchEvent(MotionEvent event)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          */
onInterceptMotionEvent(MotionEvent event)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          */
onInterceptKeyEvent(KeyEvent event)166         public boolean onInterceptKeyEvent(KeyEvent event);
167     }
168 
169     public interface OnUnhandledKeyListener {
170         /**
171          * Returns true if the key event should be consumed.
172          */
onUnhandledKey(KeyEvent event)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 
BaseGridView(Context context, AttributeSet attrs, int defStyle)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 
initBaseGridViewAttributes(Context context, AttributeSet attrs)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      */
setFocusScrollStrategy(int scrollStrategy)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      */
getFocusScrollStrategy()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      */
setWindowAlignment(int windowAlignment)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      */
getWindowAlignment()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      */
setWindowAlignmentOffset(int offset)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      */
getWindowAlignmentOffset()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      */
setWindowAlignmentOffsetPercent(float offsetPercent)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      */
getWindowAlignmentOffsetPercent()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      */
setItemAlignmentOffset(int offset)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      */
getItemAlignmentOffset()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      */
setItemAlignmentOffsetWithPadding(boolean withPadding)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      */
isItemAlignmentOffsetWithPadding()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      */
setItemAlignmentOffsetPercent(float offsetPercent)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      */
getItemAlignmentOffsetPercent()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      */
setItemAlignmentViewId(int viewId)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      */
getItemAlignmentViewId()424     public int getItemAlignmentViewId() {
425         return mLayoutManager.getItemAlignmentViewId();
426     }
427 
428     /**
429      * Sets the margin in pixels between two child items.
430      */
setItemMargin(int margin)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      */
setVerticalMargin(int margin)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      */
getVerticalMargin()447     public int getVerticalMargin() {
448         return mLayoutManager.getVerticalMargin();
449     }
450 
451     /**
452      * Sets the margin in pixels between two child items horizontally.
453      */
setHorizontalMargin(int margin)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      */
getHorizontalMargin()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      */
setOnChildLaidOutListener(OnChildLaidOutListener listener)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      */
setOnChildSelectedListener(OnChildSelectedListener listener)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      */
setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)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      */
addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)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      */
removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)519     public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)
520             {
521         mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
522     }
523 
524     /**
525      * Changes the selected item immediately without animation.
526      */
setSelectedPosition(int position)527     public void setSelectedPosition(int position) {
528         mLayoutManager.setSelection(position, 0);
529     }
530 
531     /**
532      * Changes the selected item and/or subposition immediately without animation.
533      */
setSelectedPositionWithSub(int position, int subposition)534     public void setSelectedPositionWithSub(int position, int subposition) {
535         mLayoutManager.setSelectionWithSub(position, subposition, 0);
536     }
537 
538     /**
539      * Changes the selected item immediately without animation, scrollExtra is
540      * applied in primary scroll direction.  The scrollExtra will be kept until
541      * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
542      */
setSelectedPosition(int position, int scrollExtra)543     public void setSelectedPosition(int position, int scrollExtra) {
544         mLayoutManager.setSelection(position, scrollExtra);
545     }
546 
547     /**
548      * Changes the selected item and/or subposition immediately without animation, scrollExtra is
549      * applied in primary scroll direction.  The scrollExtra will be kept until
550      * another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
551      */
setSelectedPositionWithSub(int position, int subposition, int scrollExtra)552     public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
553         mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
554     }
555 
556     /**
557      * Changes the selected item and run an animation to scroll to the target
558      * position.
559      */
setSelectedPositionSmooth(int position)560     public void setSelectedPositionSmooth(int position) {
561         mLayoutManager.setSelectionSmooth(position);
562     }
563 
564     /**
565      * Changes the selected item and/or subposition, runs an animation to scroll to the target
566      * position.
567      */
setSelectedPositionSmoothWithSub(int position, int subposition)568     public void setSelectedPositionSmoothWithSub(int position, int subposition) {
569         mLayoutManager.setSelectionSmoothWithSub(position, subposition);
570     }
571 
572     /**
573      * Perform a task on ViewHolder at given position after smooth scrolling to it.
574      * @param position Position of item in adapter.
575      * @param task Task to executed on the ViewHolder at a given position.
576      */
setSelectedPositionSmooth(final int position, final ViewHolderTask task)577     public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) {
578         if (task != null) {
579             RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
580             if (vh == null || hasPendingAdapterUpdates()) {
581                 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
582                     public void onChildViewHolderSelected(RecyclerView parent,
583                             RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
584                         if (selectedPosition == position) {
585                             removeOnChildViewHolderSelectedListener(this);
586                             task.run(child);
587                         }
588                     }
589                 });
590             } else {
591                 task.run(vh);
592             }
593         }
594         setSelectedPositionSmooth(position);
595     }
596 
597     /**
598      * Perform a task on ViewHolder at given position after scroll to it.
599      * @param position Position of item in adapter.
600      * @param task Task to executed on the ViewHolder at a given position.
601      */
setSelectedPosition(final int position, final ViewHolderTask task)602     public void setSelectedPosition(final int position, final ViewHolderTask task) {
603         if (task != null) {
604             RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
605             if (vh == null || hasPendingAdapterUpdates()) {
606                 addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
607                     public void onChildViewHolderSelected(RecyclerView parent,
608                             RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
609                         if (selectedPosition == position) {
610                             removeOnChildViewHolderSelectedListener(this);
611                             task.run(child);
612                         }
613                     }
614                 });
615             } else {
616                 task.run(vh);
617             }
618         }
619         setSelectedPosition(position);
620     }
621 
622     /**
623      * Returns the selected item position.
624      */
getSelectedPosition()625     public int getSelectedPosition() {
626         return mLayoutManager.getSelection();
627     }
628 
629     /**
630      * Returns the sub selected item position started from zero.  An item can have
631      * multiple {@link ItemAlignmentFacet}s provided by {@link RecyclerView.ViewHolder}
632      * or {@link FacetProviderAdapter}.  Zero is returned when no {@link ItemAlignmentFacet}
633      * is defined.
634      */
getSelectedSubPosition()635     public int getSelectedSubPosition() {
636         return mLayoutManager.getSubSelection();
637     }
638 
639     /**
640      * Sets whether an animation should run when a child changes size or when adding
641      * or removing a child.
642      * <p><i>Unstable API, might change later.</i>
643      */
setAnimateChildLayout(boolean animateChildLayout)644     public void setAnimateChildLayout(boolean animateChildLayout) {
645         if (mAnimateChildLayout != animateChildLayout) {
646             mAnimateChildLayout = animateChildLayout;
647             if (!mAnimateChildLayout) {
648                 mSavedItemAnimator = getItemAnimator();
649                 super.setItemAnimator(null);
650             } else {
651                 super.setItemAnimator(mSavedItemAnimator);
652             }
653         }
654     }
655 
656     /**
657      * Returns true if an animation will run when a child changes size or when
658      * adding or removing a child.
659      * <p><i>Unstable API, might change later.</i>
660      */
isChildLayoutAnimated()661     public boolean isChildLayoutAnimated() {
662         return mAnimateChildLayout;
663     }
664 
665     /**
666      * Sets the gravity used for child view positioning. Defaults to
667      * GRAVITY_TOP|GRAVITY_START.
668      *
669      * @param gravity See {@link android.view.Gravity}
670      */
setGravity(int gravity)671     public void setGravity(int gravity) {
672         mLayoutManager.setGravity(gravity);
673         requestLayout();
674     }
675 
676     @Override
onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect)677     public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
678         return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
679                 previouslyFocusedRect);
680     }
681 
682     /**
683      * Returns the x/y offsets to final position from current position if the view
684      * is selected.
685      *
686      * @param view The view to get offsets.
687      * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of Y.
688      */
getViewSelectedOffsets(View view, int[] offsets)689     public void getViewSelectedOffsets(View view, int[] offsets) {
690         mLayoutManager.getViewSelectedOffsets(view, offsets);
691     }
692 
693     @Override
getChildDrawingOrder(int childCount, int i)694     public int getChildDrawingOrder(int childCount, int i) {
695         return mLayoutManager.getChildDrawingOrder(this, childCount, i);
696     }
697 
isChildrenDrawingOrderEnabledInternal()698     final boolean isChildrenDrawingOrderEnabledInternal() {
699         return isChildrenDrawingOrderEnabled();
700     }
701 
702     @Override
focusSearch(int direction)703     public View focusSearch(int direction) {
704         if (isFocused()) {
705             // focusSearch(int) is called when GridView itself is focused.
706             // Calling focusSearch(view, int) to get next sibling of current selected child.
707             View view = mLayoutManager.findViewByPosition(mLayoutManager.getSelection());
708             if (view != null) {
709                 return focusSearch(view, direction);
710             }
711         }
712         // otherwise, go to mParent to perform focusSearch
713         return super.focusSearch(direction);
714     }
715 
716     @Override
onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect)717     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
718         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
719         mLayoutManager.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
720     }
721 
722     /**
723      * Disables or enables focus search.
724      */
setFocusSearchDisabled(boolean disabled)725     public final void setFocusSearchDisabled(boolean disabled) {
726         // LayoutManager may detachView and attachView in fastRelayout, it causes RowsFragment
727         // re-gain focus after a BACK key pressed, so block children focus during transition.
728         setDescendantFocusability(disabled ? FOCUS_BLOCK_DESCENDANTS: FOCUS_AFTER_DESCENDANTS);
729         mLayoutManager.setFocusSearchDisabled(disabled);
730     }
731 
732     /**
733      * Returns true if focus search is disabled.
734      */
isFocusSearchDisabled()735     public final boolean isFocusSearchDisabled() {
736         return mLayoutManager.isFocusSearchDisabled();
737     }
738 
739     /**
740      * Enables or disables layout.  All children will be removed when layout is
741      * disabled.
742      */
setLayoutEnabled(boolean layoutEnabled)743     public void setLayoutEnabled(boolean layoutEnabled) {
744         mLayoutManager.setLayoutEnabled(layoutEnabled);
745     }
746 
747     /**
748      * Changes and overrides children's visibility.
749      */
setChildrenVisibility(int visibility)750     public void setChildrenVisibility(int visibility) {
751         mLayoutManager.setChildrenVisibility(visibility);
752     }
753 
754     /**
755      * Enables or disables pruning of children.  Disable is useful during transition.
756      */
setPruneChild(boolean pruneChild)757     public void setPruneChild(boolean pruneChild) {
758         mLayoutManager.setPruneChild(pruneChild);
759     }
760 
761     /**
762      * Enables or disables scrolling.  Disable is useful during transition.
763      */
setScrollEnabled(boolean scrollEnabled)764     public void setScrollEnabled(boolean scrollEnabled) {
765         mLayoutManager.setScrollEnabled(scrollEnabled);
766     }
767 
768     /**
769      * Returns true if scrolling is enabled.
770      */
isScrollEnabled()771     public boolean isScrollEnabled() {
772         return mLayoutManager.isScrollEnabled();
773     }
774 
775     /**
776      * Returns true if the view at the given position has a same row sibling
777      * in front of it.  This will return true if first item view is not created.
778      * So application should check in both {@link OnChildSelectedListener} and {@link
779      * OnChildLaidOutListener}.
780      *
781      * @param position Position in adapter.
782      */
hasPreviousViewInSameRow(int position)783     public boolean hasPreviousViewInSameRow(int position) {
784         return mLayoutManager.hasPreviousViewInSameRow(position);
785     }
786 
787     /**
788      * Enables or disables the default "focus draw at last" order rule.
789      */
setFocusDrawingOrderEnabled(boolean enabled)790     public void setFocusDrawingOrderEnabled(boolean enabled) {
791         super.setChildrenDrawingOrderEnabled(enabled);
792     }
793 
794     /**
795      * Returns true if default "focus draw at last" order rule is enabled.
796      */
isFocusDrawingOrderEnabled()797     public boolean isFocusDrawingOrderEnabled() {
798         return super.isChildrenDrawingOrderEnabled();
799     }
800 
801     /**
802      * Sets the touch intercept listener.
803      */
setOnTouchInterceptListener(OnTouchInterceptListener listener)804     public void setOnTouchInterceptListener(OnTouchInterceptListener listener) {
805         mOnTouchInterceptListener = listener;
806     }
807 
808     /**
809      * Sets the generic motion intercept listener.
810      */
setOnMotionInterceptListener(OnMotionInterceptListener listener)811     public void setOnMotionInterceptListener(OnMotionInterceptListener listener) {
812         mOnMotionInterceptListener = listener;
813     }
814 
815     /**
816      * Sets the key intercept listener.
817      */
setOnKeyInterceptListener(OnKeyInterceptListener listener)818     public void setOnKeyInterceptListener(OnKeyInterceptListener listener) {
819         mOnKeyInterceptListener = listener;
820     }
821 
822     /**
823      * Sets the unhandled key listener.
824      */
setOnUnhandledKeyListener(OnUnhandledKeyListener listener)825     public void setOnUnhandledKeyListener(OnUnhandledKeyListener listener) {
826         mOnUnhandledKeyListener = listener;
827     }
828 
829     /**
830      * Returns the unhandled key listener.
831      */
getOnUnhandledKeyListener()832     public OnUnhandledKeyListener getOnUnhandledKeyListener() {
833         return mOnUnhandledKeyListener;
834     }
835 
836     @Override
dispatchKeyEvent(KeyEvent event)837     public boolean dispatchKeyEvent(KeyEvent event) {
838         if (mOnKeyInterceptListener != null && mOnKeyInterceptListener.onInterceptKeyEvent(event)) {
839             return true;
840         }
841         if (super.dispatchKeyEvent(event)) {
842             return true;
843         }
844         if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) {
845             return true;
846         }
847         return false;
848     }
849 
850     @Override
dispatchTouchEvent(MotionEvent event)851     public boolean dispatchTouchEvent(MotionEvent event) {
852         if (mOnTouchInterceptListener != null) {
853             if (mOnTouchInterceptListener.onInterceptTouchEvent(event)) {
854                 return true;
855             }
856         }
857         return super.dispatchTouchEvent(event);
858     }
859 
860     @Override
dispatchGenericFocusedEvent(MotionEvent event)861     public boolean dispatchGenericFocusedEvent(MotionEvent event) {
862         if (mOnMotionInterceptListener != null) {
863             if (mOnMotionInterceptListener.onInterceptMotionEvent(event)) {
864                 return true;
865             }
866         }
867         return super.dispatchGenericFocusedEvent(event);
868     }
869 
870     /**
871      * Returns the policy for saving children.
872      *
873      * @return policy, one of {@link #SAVE_NO_CHILD}
874      * {@link #SAVE_ON_SCREEN_CHILD} {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
875      */
getSaveChildrenPolicy()876     public final int getSaveChildrenPolicy() {
877         return mLayoutManager.mChildrenStates.getSavePolicy();
878     }
879 
880     /**
881      * Returns the limit used when when {@link #getSaveChildrenPolicy()} is
882      *         {@link #SAVE_LIMITED_CHILD}
883      */
getSaveChildrenLimitNumber()884     public final int getSaveChildrenLimitNumber() {
885         return mLayoutManager.mChildrenStates.getLimitNumber();
886     }
887 
888     /**
889      * Sets the policy for saving children.
890      * @param savePolicy One of {@link #SAVE_NO_CHILD} {@link #SAVE_ON_SCREEN_CHILD}
891      * {@link #SAVE_LIMITED_CHILD} {@link #SAVE_ALL_CHILD}.
892      */
setSaveChildrenPolicy(int savePolicy)893     public final void setSaveChildrenPolicy(int savePolicy) {
894         mLayoutManager.mChildrenStates.setSavePolicy(savePolicy);
895     }
896 
897     /**
898      * Sets the limit number when {@link #getSaveChildrenPolicy()} is {@link #SAVE_LIMITED_CHILD}.
899      */
setSaveChildrenLimitNumber(int limitNumber)900     public final void setSaveChildrenLimitNumber(int limitNumber) {
901         mLayoutManager.mChildrenStates.setLimitNumber(limitNumber);
902     }
903 
904     @Override
hasOverlappingRendering()905     public boolean hasOverlappingRendering() {
906         return mHasOverlappingRendering;
907     }
908 
setHasOverlappingRendering(boolean hasOverlapping)909     public void setHasOverlappingRendering(boolean hasOverlapping) {
910         mHasOverlappingRendering = hasOverlapping;
911     }
912 
913     /**
914      * Notify layout manager that layout directionality has been updated
915      */
916     @Override
onRtlPropertiesChanged(int layoutDirection)917     public void onRtlPropertiesChanged(int layoutDirection) {
918         mLayoutManager.onRtlPropertiesChanged(layoutDirection);
919     }
920 
921     @Override
setRecyclerListener(RecyclerView.RecyclerListener listener)922     public void setRecyclerListener(RecyclerView.RecyclerListener listener) {
923         mChainedRecyclerListener = listener;
924     }
925 
926     /**
927      * Sets pixels of extra space for layout child in invisible area.
928      *
929      * @param extraLayoutSpace  Pixels of extra space for layout invisible child.
930      *                          Must be bigger or equals to 0.
931      * @hide
932      */
setExtraLayoutSpace(int extraLayoutSpace)933     public void setExtraLayoutSpace(int extraLayoutSpace) {
934         mLayoutManager.setExtraLayoutSpace(extraLayoutSpace);
935     }
936 
937     /**
938      * Returns pixels of extra space for layout child in invisible area.
939      *
940      * @hide
941      */
getExtraLayoutSpace()942     public int getExtraLayoutSpace() {
943         return mLayoutManager.getExtraLayoutSpace();
944     }
945 
946 }
947