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