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