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