BaseGridView.java revision 9421aa6ca7de4174ddbe2e10fbb05cb31685ffcc
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.View;
23import android.view.ViewGroup;
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    private int[] mMeasuredSize = new int[2];
108
109    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
110        super(context, attrs, defStyle);
111        mLayoutManager = new GridLayoutManager(this);
112        setLayoutManager(mLayoutManager);
113        setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
114        setHasFixedSize(true);
115        setChildrenDrawingOrderEnabled(true);
116    }
117
118    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
119        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
120        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
121        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
122        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
123        mLayoutManager.setVerticalMargin(
124                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
125        mLayoutManager.setHorizontalMargin(
126                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
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 offset percent for item alignment in addition to {@link
254     * #getItemAlignmentOffset()}.
255     *
256     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
257     *        width from the low edge. Use
258     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
259     */
260    public void setItemAlignmentOffsetPercent(float offsetPercent) {
261        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
262        requestLayout();
263    }
264
265    /**
266     * Get offset percent for item alignment in addition to {@link
267     * #getItemAlignmentOffset()}.
268     *
269     * @return Percentage to offset. E.g., 40 means 40% of the width from the
270     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
271     *         disabled. Default value is 50.
272     */
273    public float getItemAlignmentOffsetPercent() {
274        return mLayoutManager.getItemAlignmentOffsetPercent();
275    }
276
277    /**
278     * Set the id of the view to align with. Use zero (default) for the item
279     * view itself.
280     */
281    public void setItemAlignmentViewId(int viewId) {
282        mLayoutManager.setItemAlignmentViewId(viewId);
283    }
284
285    /**
286     * Get the id of the view to align with, or zero for the item view itself.
287     */
288    public int getItemAlignmentViewId() {
289        return mLayoutManager.getItemAlignmentViewId();
290    }
291
292    /**
293     * Set the margin in pixels between two child items.
294     */
295    public void setItemMargin(int margin) {
296        mLayoutManager.setItemMargin(margin);
297        requestLayout();
298    }
299
300    /**
301     * Set the margin in pixels between two child items vertically.
302     */
303    public void setVerticalMargin(int margin) {
304        mLayoutManager.setVerticalMargin(margin);
305        requestLayout();
306    }
307
308    /**
309     * Get the margin in pixels between two child items vertically.
310     */
311    public int getVerticalMargin() {
312        return mLayoutManager.getVerticalMargin();
313    }
314
315    /**
316     * Set the margin in pixels between two child items horizontally.
317     */
318    public void setHorizontalMargin(int margin) {
319        mLayoutManager.setHorizontalMargin(margin);
320        requestLayout();
321    }
322
323    /**
324     * Get the margin in pixels between two child items horizontally.
325     */
326    public int getHorizontalMargin() {
327        return mLayoutManager.getHorizontalMargin();
328    }
329
330    /**
331     * Register a callback to be invoked when an item in BaseGridView has
332     * been selected.
333     */
334    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
335        mLayoutManager.setOnChildSelectedListener(listener);
336    }
337
338    /**
339     * Change the selected item immediately without animation.
340     */
341    public void setSelectedPosition(int position) {
342        mLayoutManager.setSelection(this, position);
343    }
344
345    /**
346     * Change the selected item and run an animation to scroll to the target
347     * position.
348     */
349    public void setSelectedPositionSmooth(int position) {
350        mLayoutManager.setSelectionSmooth(this, position);
351    }
352
353    /**
354     * Get the selected item position.
355     */
356    public int getSelectedPosition() {
357        return mLayoutManager.getSelection();
358    }
359
360    /**
361     * Set if an animation should run when a child changes size or when adding
362     * or removing a child.
363     * <p><i>Unstable API, might change later.</i>
364     */
365    public void setAnimateChildLayout(boolean animateChildLayout) {
366        mLayoutManager.setAnimateChildLayout(animateChildLayout);
367    }
368
369    /**
370     * Return true if an animation will run when a child changes size or when
371     * adding or removing a child.
372     * <p><i>Unstable API, might change later.</i>
373     */
374    public boolean isChildLayoutAnimated() {
375        return mLayoutManager.isChildLayoutAnimated();
376    }
377
378    /**
379     * Set an interpolator for the animation when a child changes size or when
380     * adding or removing a child.
381     * <p><i>Unstable API, might change later.</i>
382     */
383    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
384        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
385    }
386
387    /**
388     * Get the interpolator for the animation 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 Interpolator getChildLayoutAnimationInterpolator() {
393        return mLayoutManager.getChildLayoutAnimationInterpolator();
394    }
395
396    /**
397     * Set the duration of the animation when a child changes size or when
398     * adding or removing a child.
399     * <p><i>Unstable API, might change later.</i>
400     */
401    public void setChildLayoutAnimationDuration(long duration) {
402        mLayoutManager.setChildLayoutAnimationDuration(duration);
403    }
404
405    /**
406     * Get the duration of the animation when a child changes size or when
407     * adding or removing a child.
408     * <p><i>Unstable API, might change later.</i>
409     */
410    public long getChildLayoutAnimationDuration() {
411        return mLayoutManager.getChildLayoutAnimationDuration();
412    }
413
414    /**
415     * Describes how the child views are positioned. Defaults to
416     * GRAVITY_TOP|GRAVITY_LEFT.
417     *
418     * @param gravity See {@link android.view.Gravity}
419     */
420    public void setGravity(int gravity) {
421        mLayoutManager.setGravity(gravity);
422        requestLayout();
423    }
424
425    @Override
426    protected final void onMeasure(int widthSpec, int heightSpec) {
427        mLayoutManager.gridOnMeasure(widthSpec, heightSpec, mMeasuredSize);
428        setMeasuredDimension(mMeasuredSize[0], mMeasuredSize[1]);
429    }
430
431    @Override
432    public void setDescendantFocusability (int focusability) {
433        // enforce FOCUS_AFTER_DESCENDANTS
434        super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
435    }
436
437    @Override
438    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
439        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
440                previouslyFocusedRect);
441    }
442
443    /**
444     * Get the x/y offsets to final position from current position if the view
445     * is selected.
446     *
447     * @param view The view to get offsets.
448     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
449     *        Y.
450     */
451    public void getViewSelectedOffsets(View view, int[] offsets) {
452        mLayoutManager.getViewSelectedOffsets(view, offsets);
453    }
454
455    @Override
456    public int getChildDrawingOrder(int childCount, int i) {
457        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
458    }
459
460    final boolean isChildrenDrawingOrderEnabledInternal() {
461        return isChildrenDrawingOrderEnabled();
462    }
463}
464