BaseGridView.java revision 4200ae975bfd7e4dc2e5c2d838c070470fcb3e2a
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.widget;
15
16import android.content.Context;
17import android.content.res.TypedArray;
18import android.graphics.Rect;
19import android.support.v17.leanback.R;
20import android.support.v7.widget.RecyclerView;
21import android.util.AttributeSet;
22import android.view.Gravity;
23import android.view.View;
24
25/**
26 * Base class for vertically and horizontally scrolling lists. The items come
27 * from the {@link RecyclerView.Adapter} associated with this view.
28 * @hide
29 */
30abstract class BaseGridView extends RecyclerView {
31
32    /**
33     * Always keep focused item at a aligned position.  Developer can use
34     * WINDOW_ALIGN_XXX and ITEM_ALIGN_XXX to define how focused item is aligned.
35     * In this mode, the last focused position will be remembered and restored when focus
36     * is back to the view.
37     */
38    public final static int FOCUS_SCROLL_ALIGNED = 0;
39
40    /**
41     * Scroll to make the focused item inside client area.
42     */
43    public final static int FOCUS_SCROLL_ITEM = 1;
44
45    /**
46     * Scroll a page of items when focusing to item outside the client area.
47     * The page size matches the client area size of RecyclerView.
48     */
49    public final static int FOCUS_SCROLL_PAGE = 2;
50
51    /**
52     * The first item is aligned with the low edge of the viewport. When
53     * navigating away from the first item, the focus maintains a middle
54     * location.
55     * <p>
56     * The middle location is calculated by "windowAlignOffset" and
57     * "windowAlignOffsetPercent"; if neither of these two is defined, the
58     * default value is 1/2 of the size.
59     */
60    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
61
62    /**
63     * The last item is aligned with the high edge of the viewport when
64     * navigating to the end of list. When navigating away from the end, the
65     * focus maintains a middle location.
66     * <p>
67     * The middle location is calculated by "windowAlignOffset" and
68     * "windowAlignOffsetPercent"; if neither of these two is defined, the
69     * default value is 1/2 of the size.
70     */
71    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
72
73    /**
74     * The first item and last item are aligned with the two edges of the
75     * viewport. When navigating in the middle of list, the focus maintains a
76     * middle location.
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_BOTH_EDGE =
83            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
84
85    /**
86     * The focused item always stays in a middle location.
87     * <p>
88     * The middle location is calculated by "windowAlignOffset" and
89     * "windowAlignOffsetPercent"; if neither of these two is defined, the
90     * default value is 1/2 of the size.
91     */
92    public final static int WINDOW_ALIGN_NO_EDGE = 0;
93
94    /**
95     * Value indicates that percent is not used.
96     */
97    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
98
99    /**
100     * Value indicates that percent is not used.
101     */
102    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1;
103
104    protected final GridLayoutManager mLayoutManager;
105
106    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
107        super(context, attrs, defStyle);
108        mLayoutManager = new GridLayoutManager(this);
109        setLayoutManager(mLayoutManager);
110        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
111        setHasFixedSize(true);
112        setChildrenDrawingOrderEnabled(true);
113    }
114
115    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
116        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
117        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
118        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
119        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
120        mLayoutManager.setVerticalMargin(
121                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
122        mLayoutManager.setHorizontalMargin(
123                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
124        if (a.hasValue(R.styleable.lbBaseGridView_android_gravity)) {
125            setGravity(a.getInt(R.styleable.lbBaseGridView_android_gravity, Gravity.NO_GRAVITY));
126        }
127        a.recycle();
128    }
129
130    /**
131     * Set the strategy used to scroll in response to item focus changing:
132     * <ul>
133     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
134     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
135     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
136     * </ul>
137     */
138    public void setFocusScrollStrategy(int scrollStrategy) {
139        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
140            && scrollStrategy != FOCUS_SCROLL_PAGE) {
141            throw new IllegalArgumentException("Invalid scrollStrategy");
142        }
143        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
144        requestLayout();
145    }
146
147    /**
148     * Returns the strategy used to scroll in response to item focus changing.
149     * <ul>
150     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
151     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
152     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
153     * </ul>
154     */
155    public int getFocusScrollStrategy() {
156        return mLayoutManager.getFocusScrollStrategy();
157    }
158
159    /**
160     * Set how the focused item gets aligned in the view.
161     *
162     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
163     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
164     *        {@link #WINDOW_ALIGN_NO_EDGE}.
165     */
166    public void setWindowAlignment(int windowAlignment) {
167        mLayoutManager.setWindowAlignment(windowAlignment);
168        requestLayout();
169    }
170
171    /**
172     * Get how the focused item gets aligned in the view.
173     *
174     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
175     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
176     */
177    public int getWindowAlignment() {
178        return mLayoutManager.getWindowAlignment();
179    }
180
181    /**
182     * Set the absolute offset in pixels for window alignment.
183     *
184     * @param offset The number of pixels to offset. Can be negative for
185     *        alignment from the high edge, or positive for alignment from the
186     *        low edge.
187     */
188    public void setWindowAlignmentOffset(int offset) {
189        mLayoutManager.setWindowAlignmentOffset(offset);
190        requestLayout();
191    }
192
193    /**
194     * Get the absolute offset in pixels for window alignment.
195     *
196     * @return The number of pixels to offset. Will be negative for alignment
197     *         from the high edge, or positive for alignment from the low edge.
198     *         Default value is 0.
199     */
200    public int getWindowAlignmentOffset() {
201        return mLayoutManager.getWindowAlignmentOffset();
202    }
203
204    /**
205     * Set offset percent for window alignment in addition to {@link
206     * #getWindowAlignmentOffset()}.
207     *
208     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
209     *        width from low edge. Use
210     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
211     */
212    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
213        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
214        requestLayout();
215    }
216
217    /**
218     * Get offset percent for window alignment in addition to
219     * {@link #getWindowAlignmentOffset()}.
220     *
221     * @return Percentage to offset. E.g., 40 means 40% of the width from the
222     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
223     *         disabled. Default value is 50.
224     */
225    public float getWindowAlignmentOffsetPercent() {
226        return mLayoutManager.getWindowAlignmentOffsetPercent();
227    }
228
229    /**
230     * Set the absolute offset in pixels for item alignment.
231     *
232     * @param offset The number of pixels to offset. Can be negative for
233     *        alignment from the high edge, or positive for alignment from the
234     *        low edge.
235     */
236    public void setItemAlignmentOffset(int offset) {
237        mLayoutManager.setItemAlignmentOffset(offset);
238        requestLayout();
239    }
240
241    /**
242     * Get the absolute offset in pixels for item alignment.
243     *
244     * @return The number of pixels to offset. Will be negative for alignment
245     *         from the high edge, or positive for alignment from the low edge.
246     *         Default value is 0.
247     */
248    public int getItemAlignmentOffset() {
249        return mLayoutManager.getItemAlignmentOffset();
250    }
251
252    /**
253     * Set to true if include padding in calculating item align offset.
254     *
255     * @param withPadding When it is true: we include left/top padding for positive
256     *          item offset, include right/bottom padding for negative item offset.
257     */
258    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
259        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
260        requestLayout();
261    }
262
263    /**
264     * Returns true if include padding in calculating item align offset.
265     */
266    public boolean isItemAlignmentOffsetWithPadding() {
267        return mLayoutManager.isItemAlignmentOffsetWithPadding();
268    }
269
270    /**
271     * Set offset percent for item alignment in addition to {@link
272     * #getItemAlignmentOffset()}.
273     *
274     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
275     *        width from the low edge. Use
276     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
277     */
278    public void setItemAlignmentOffsetPercent(float offsetPercent) {
279        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
280        requestLayout();
281    }
282
283    /**
284     * Get offset percent for item alignment in addition to {@link
285     * #getItemAlignmentOffset()}.
286     *
287     * @return Percentage to offset. E.g., 40 means 40% of the width from the
288     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
289     *         disabled. Default value is 50.
290     */
291    public float getItemAlignmentOffsetPercent() {
292        return mLayoutManager.getItemAlignmentOffsetPercent();
293    }
294
295    /**
296     * Set the id of the view to align with. Use zero (default) for the item
297     * view itself.
298     */
299    public void setItemAlignmentViewId(int viewId) {
300        mLayoutManager.setItemAlignmentViewId(viewId);
301    }
302
303    /**
304     * Get the id of the view to align with, or zero for the item view itself.
305     */
306    public int getItemAlignmentViewId() {
307        return mLayoutManager.getItemAlignmentViewId();
308    }
309
310    /**
311     * Set the margin in pixels between two child items.
312     */
313    public void setItemMargin(int margin) {
314        mLayoutManager.setItemMargin(margin);
315        requestLayout();
316    }
317
318    /**
319     * Set the margin in pixels between two child items vertically.
320     */
321    public void setVerticalMargin(int margin) {
322        mLayoutManager.setVerticalMargin(margin);
323        requestLayout();
324    }
325
326    /**
327     * Get the margin in pixels between two child items vertically.
328     */
329    public int getVerticalMargin() {
330        return mLayoutManager.getVerticalMargin();
331    }
332
333    /**
334     * Set the margin in pixels between two child items horizontally.
335     */
336    public void setHorizontalMargin(int margin) {
337        mLayoutManager.setHorizontalMargin(margin);
338        requestLayout();
339    }
340
341    /**
342     * Get the margin in pixels between two child items horizontally.
343     */
344    public int getHorizontalMargin() {
345        return mLayoutManager.getHorizontalMargin();
346    }
347
348    /**
349     * Register a callback to be invoked when an item in BaseGridView has
350     * been selected.
351     */
352    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
353        mLayoutManager.setOnChildSelectedListener(listener);
354    }
355
356    /**
357     * Change the selected item immediately without animation.
358     */
359    public void setSelectedPosition(int position) {
360        mLayoutManager.setSelection(this, position);
361    }
362
363    /**
364     * Change the selected item and run an animation to scroll to the target
365     * position.
366     */
367    public void setSelectedPositionSmooth(int position) {
368        mLayoutManager.setSelectionSmooth(this, position);
369    }
370
371    /**
372     * Get the selected item position.
373     */
374    public int getSelectedPosition() {
375        return mLayoutManager.getSelection();
376    }
377
378    /**
379     * Set if an animation should run when a child changes size or when adding
380     * or removing a child.
381     * <p><i>Unstable API, might change later.</i>
382     */
383    public void setAnimateChildLayout(boolean animateChildLayout) {
384        mLayoutManager.setAnimateChildLayout(animateChildLayout);
385    }
386
387    /**
388     * Return true if an animation will run when a child changes size or when
389     * adding or removing a child.
390     * <p><i>Unstable API, might change later.</i>
391     */
392    public boolean isChildLayoutAnimated() {
393        return mLayoutManager.isChildLayoutAnimated();
394    }
395
396    /**
397     * Describes how the child views are positioned. Defaults to
398     * GRAVITY_TOP|GRAVITY_LEFT.
399     *
400     * @param gravity See {@link android.view.Gravity}
401     */
402    public void setGravity(int gravity) {
403        mLayoutManager.setGravity(gravity);
404        requestLayout();
405    }
406
407    @Override
408    public void setDescendantFocusability (int focusability) {
409        // enforce FOCUS_AFTER_DESCENDANTS
410        super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
411    }
412
413    @Override
414    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
415        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
416                previouslyFocusedRect);
417    }
418
419    /**
420     * Get the x/y offsets to final position from current position if the view
421     * is selected.
422     *
423     * @param view The view to get offsets.
424     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
425     *        Y.
426     */
427    public void getViewSelectedOffsets(View view, int[] offsets) {
428        mLayoutManager.getViewSelectedOffsets(view, offsets);
429    }
430
431    @Override
432    public int getChildDrawingOrder(int childCount, int i) {
433        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
434    }
435
436    final boolean isChildrenDrawingOrderEnabledInternal() {
437        return isChildrenDrawingOrderEnabled();
438    }
439
440    /**
441     * Disable or enable focus search.
442     */
443    public final void setFocusSearchDisabled(boolean disabled) {
444        mLayoutManager.setFocusSearchDisabled(disabled);
445    }
446
447    /**
448     * Return true if focus search is disabled.
449     */
450    public final boolean isFocusSearchDisabled() {
451        return mLayoutManager.isFocusSearchDisabled();
452    }
453
454    /**
455     * Enable or disable layout.  All children will be removed when layout is
456     * disabled.
457     */
458    public void setLayoutEnabled(boolean layoutEnabled) {
459        mLayoutManager.setLayoutEnabled(layoutEnabled);
460    }
461
462    /**
463     * Enable or disable pruning child.  Disable is useful during transition.
464     */
465    public void setPruneChild(boolean pruneChild) {
466        mLayoutManager.setPruneChild(pruneChild);
467    }
468
469    /**
470     * Get if view has same row sibling next to it.
471     *
472     * @param position Position in adapter.
473     */
474    public boolean hasNextViewInSameRow(int position) {
475        return mLayoutManager.hasNextViewInSameRow(position);
476    }
477
478    /**
479     * Get if view has same row sibling in front of it.
480     *
481     * @param position Position in adapter.
482     */
483    public boolean hasPreviousViewInSameRow(int position) {
484        return mLayoutManager.hasPreviousViewInSameRow(position);
485    }
486}
487