BaseGridView.java revision e43e9266c4b7e4902fefb5d2a0cacca90a3d2681
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 to true if include padding in calculating item align offset.
255     *
256     * @param withPadding When it is true: we include left/top padding for positive
257     *          item offset, include right/bottom padding for negative item offset.
258     */
259    public void setItemAlignmentOffsetWithPadding(boolean withPadding) {
260        mLayoutManager.setItemAlignmentOffsetWithPadding(withPadding);
261        requestLayout();
262    }
263
264    /**
265     * Returns true if include padding in calculating item align offset.
266     */
267    public boolean isItemAlignmentOffsetWithPadding() {
268        return mLayoutManager.isItemAlignmentOffsetWithPadding();
269    }
270
271    /**
272     * Set offset percent for item alignment in addition to {@link
273     * #getItemAlignmentOffset()}.
274     *
275     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
276     *        width from the low edge. Use
277     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
278     */
279    public void setItemAlignmentOffsetPercent(float offsetPercent) {
280        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
281        requestLayout();
282    }
283
284    /**
285     * Get offset percent for item alignment in addition to {@link
286     * #getItemAlignmentOffset()}.
287     *
288     * @return Percentage to offset. E.g., 40 means 40% of the width from the
289     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
290     *         disabled. Default value is 50.
291     */
292    public float getItemAlignmentOffsetPercent() {
293        return mLayoutManager.getItemAlignmentOffsetPercent();
294    }
295
296    /**
297     * Set the id of the view to align with. Use zero (default) for the item
298     * view itself.
299     */
300    public void setItemAlignmentViewId(int viewId) {
301        mLayoutManager.setItemAlignmentViewId(viewId);
302    }
303
304    /**
305     * Get the id of the view to align with, or zero for the item view itself.
306     */
307    public int getItemAlignmentViewId() {
308        return mLayoutManager.getItemAlignmentViewId();
309    }
310
311    /**
312     * Set the margin in pixels between two child items.
313     */
314    public void setItemMargin(int margin) {
315        mLayoutManager.setItemMargin(margin);
316        requestLayout();
317    }
318
319    /**
320     * Set the margin in pixels between two child items vertically.
321     */
322    public void setVerticalMargin(int margin) {
323        mLayoutManager.setVerticalMargin(margin);
324        requestLayout();
325    }
326
327    /**
328     * Get the margin in pixels between two child items vertically.
329     */
330    public int getVerticalMargin() {
331        return mLayoutManager.getVerticalMargin();
332    }
333
334    /**
335     * Set the margin in pixels between two child items horizontally.
336     */
337    public void setHorizontalMargin(int margin) {
338        mLayoutManager.setHorizontalMargin(margin);
339        requestLayout();
340    }
341
342    /**
343     * Get the margin in pixels between two child items horizontally.
344     */
345    public int getHorizontalMargin() {
346        return mLayoutManager.getHorizontalMargin();
347    }
348
349    /**
350     * Register a callback to be invoked when an item in BaseGridView has
351     * been selected.
352     */
353    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
354        mLayoutManager.setOnChildSelectedListener(listener);
355    }
356
357    /**
358     * Change the selected item immediately without animation.
359     */
360    public void setSelectedPosition(int position) {
361        mLayoutManager.setSelection(this, position);
362    }
363
364    /**
365     * Change the selected item and run an animation to scroll to the target
366     * position.
367     */
368    public void setSelectedPositionSmooth(int position) {
369        mLayoutManager.setSelectionSmooth(this, position);
370    }
371
372    /**
373     * Get the selected item position.
374     */
375    public int getSelectedPosition() {
376        return mLayoutManager.getSelection();
377    }
378
379    /**
380     * Set if an animation should run when a child changes size or when adding
381     * or removing a child.
382     * <p><i>Unstable API, might change later.</i>
383     */
384    public void setAnimateChildLayout(boolean animateChildLayout) {
385        mLayoutManager.setAnimateChildLayout(animateChildLayout);
386    }
387
388    /**
389     * Return true if an animation will run 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 boolean isChildLayoutAnimated() {
394        return mLayoutManager.isChildLayoutAnimated();
395    }
396
397    /**
398     * Set an interpolator for 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 setChildLayoutAnimationInterpolator(Interpolator interpolator) {
403        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
404    }
405
406    /**
407     * Get the interpolator for 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 Interpolator getChildLayoutAnimationInterpolator() {
412        return mLayoutManager.getChildLayoutAnimationInterpolator();
413    }
414
415    /**
416     * Set the duration of the animation when a child changes size or when
417     * adding or removing a child.
418     * <p><i>Unstable API, might change later.</i>
419     */
420    public void setChildLayoutAnimationDuration(long duration) {
421        mLayoutManager.setChildLayoutAnimationDuration(duration);
422    }
423
424    /**
425     * Get the duration of the animation when a child changes size or when
426     * adding or removing a child.
427     * <p><i>Unstable API, might change later.</i>
428     */
429    public long getChildLayoutAnimationDuration() {
430        return mLayoutManager.getChildLayoutAnimationDuration();
431    }
432
433    /**
434     * Describes how the child views are positioned. Defaults to
435     * GRAVITY_TOP|GRAVITY_LEFT.
436     *
437     * @param gravity See {@link android.view.Gravity}
438     */
439    public void setGravity(int gravity) {
440        mLayoutManager.setGravity(gravity);
441        requestLayout();
442    }
443
444    @Override
445    public void setDescendantFocusability (int focusability) {
446        // enforce FOCUS_AFTER_DESCENDANTS
447        super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
448    }
449
450    @Override
451    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
452        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
453                previouslyFocusedRect);
454    }
455
456    /**
457     * Get the x/y offsets to final position from current position if the view
458     * is selected.
459     *
460     * @param view The view to get offsets.
461     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
462     *        Y.
463     */
464    public void getViewSelectedOffsets(View view, int[] offsets) {
465        mLayoutManager.getViewSelectedOffsets(view, offsets);
466    }
467
468    @Override
469    public int getChildDrawingOrder(int childCount, int i) {
470        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
471    }
472
473    final boolean isChildrenDrawingOrderEnabledInternal() {
474        return isChildrenDrawingOrderEnabled();
475    }
476
477    /**
478     * Disable or enable focus search.
479     */
480    public final void setFocusSearchDisabled(boolean disabled) {
481        mLayoutManager.setFocusSearchDisabled(disabled);
482    }
483
484    /**
485     * Return true if focus search is disabled.
486     */
487    public final boolean isFocusSearchDisabled() {
488        return mLayoutManager.isFocusSearchDisabled();
489    }
490
491    /**
492     * Enable or disable layout.  All children will be removed when layout is
493     * disabled.
494     */
495    public void setLayoutEnabled(boolean layoutEnabled) {
496        mLayoutManager.setLayoutEnabled(layoutEnabled);
497    }
498
499    /**
500     * Enable or disable pruning child.  Disable is useful during transition.
501     */
502    public void setPruneChild(boolean pruneChild) {
503        mLayoutManager.setPruneChild(pruneChild);
504    }
505
506    /**
507     * Get if view has same row sibling next to it.
508     *
509     * @param position Position in adapter.
510     */
511    public boolean hasNextViewInSameRow(int position) {
512        return mLayoutManager.hasNextViewInSameRow(position);
513    }
514
515    /**
516     * Get if view has same row sibling in front of it.
517     *
518     * @param position Position in adapter.
519     */
520    public boolean hasPreviousViewInSameRow(int position) {
521        return mLayoutManager.hasPreviousViewInSameRow(position);
522    }
523}
524