BaseGridView.java revision 5b1f117209e8a38d6d6b004c3c0d0285acc9b27f
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        setHasFixedSize(true);
114        setChildrenDrawingOrderEnabled(true);
115    }
116
117    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
118        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
119        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
120        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
121        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
122        mLayoutManager.setVerticalMargin(
123                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
124        mLayoutManager.setHorizontalMargin(
125                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
126        a.recycle();
127    }
128
129    /**
130     * Set the strategy used to scroll in response to item focus changing:
131     * <ul>
132     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
133     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
134     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
135     * </ul>
136     */
137    public void setFocusScrollStrategy(int scrollStrategy) {
138        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
139            && scrollStrategy != FOCUS_SCROLL_PAGE) {
140            throw new IllegalArgumentException("Invalid scrollStrategy");
141        }
142        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
143        requestLayout();
144    }
145
146    /**
147     * Returns the strategy used to scroll in response to item focus changing.
148     * <ul>
149     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
150     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
151     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
152     * </ul>
153     */
154    public int getFocusScrollStrategy() {
155        return mLayoutManager.getFocusScrollStrategy();
156    }
157
158    /**
159     * Set how the focused item gets aligned in the view.
160     *
161     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
162     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
163     *        {@link #WINDOW_ALIGN_NO_EDGE}.
164     */
165    public void setWindowAlignment(int windowAlignment) {
166        mLayoutManager.setWindowAlignment(windowAlignment);
167        requestLayout();
168    }
169
170    /**
171     * Get how the focused item gets aligned in the view.
172     *
173     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
174     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
175     */
176    public int getWindowAlignment() {
177        return mLayoutManager.getWindowAlignment();
178    }
179
180    /**
181     * Set the absolute offset in pixels for window alignment.
182     *
183     * @param offset The number of pixels to offset. Can be negative for
184     *        alignment from the high edge, or positive for alignment from the
185     *        low edge.
186     */
187    public void setWindowAlignmentOffset(int offset) {
188        mLayoutManager.setWindowAlignmentOffset(offset);
189        requestLayout();
190    }
191
192    /**
193     * Get the absolute offset in pixels for window alignment.
194     *
195     * @return The number of pixels to offset. Will be negative for alignment
196     *         from the high edge, or positive for alignment from the low edge.
197     *         Default value is 0.
198     */
199    public int getWindowAlignmentOffset() {
200        return mLayoutManager.getWindowAlignmentOffset();
201    }
202
203    /**
204     * Set offset percent for window alignment in addition to {@link
205     * #getWindowAlignmentOffset()}.
206     *
207     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
208     *        width from low edge. Use
209     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
210     */
211    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
212        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
213        requestLayout();
214    }
215
216    /**
217     * Get offset percent for window alignment in addition to
218     * {@link #getWindowAlignmentOffset()}.
219     *
220     * @return Percentage to offset. E.g., 40 means 40% of the width from the
221     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
222     *         disabled. Default value is 50.
223     */
224    public float getWindowAlignmentOffsetPercent() {
225        return mLayoutManager.getWindowAlignmentOffsetPercent();
226    }
227
228    /**
229     * Set the absolute offset in pixels for item alignment.
230     *
231     * @param offset The number of pixels to offset. Can be negative for
232     *        alignment from the high edge, or positive for alignment from the
233     *        low edge.
234     */
235    public void setItemAlignmentOffset(int offset) {
236        mLayoutManager.setItemAlignmentOffset(offset);
237        requestLayout();
238    }
239
240    /**
241     * Get the absolute offset in pixels for item alignment.
242     *
243     * @return The number of pixels to offset. Will be negative for alignment
244     *         from the high edge, or positive for alignment from the low edge.
245     *         Default value is 0.
246     */
247    public int getItemAlignmentOffset() {
248        return mLayoutManager.getItemAlignmentOffset();
249    }
250
251    /**
252     * Set offset percent for item alignment in addition to {@link
253     * #getItemAlignmentOffset()}.
254     *
255     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
256     *        width from the low edge. Use
257     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
258     */
259    public void setItemAlignmentOffsetPercent(float offsetPercent) {
260        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
261        requestLayout();
262    }
263
264    /**
265     * Get offset percent for item alignment in addition to {@link
266     * #getItemAlignmentOffset()}.
267     *
268     * @return Percentage to offset. E.g., 40 means 40% of the width from the
269     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
270     *         disabled. Default value is 50.
271     */
272    public float getItemAlignmentOffsetPercent() {
273        return mLayoutManager.getItemAlignmentOffsetPercent();
274    }
275
276    /**
277     * Set the id of the view to align with. Use zero (default) for the item
278     * view itself.
279     */
280    public void setItemAlignmentViewId(int viewId) {
281        mLayoutManager.setItemAlignmentViewId(viewId);
282    }
283
284    /**
285     * Get the id of the view to align with, or zero for the item view itself.
286     */
287    public int getItemAlignmentViewId() {
288        return mLayoutManager.getItemAlignmentViewId();
289    }
290
291    /**
292     * Set the margin in pixels between two child items.
293     */
294    public void setItemMargin(int margin) {
295        mLayoutManager.setItemMargin(margin);
296        requestLayout();
297    }
298
299    /**
300     * Set the margin in pixels between two child items vertically.
301     */
302    public void setVerticalMargin(int margin) {
303        mLayoutManager.setVerticalMargin(margin);
304        requestLayout();
305    }
306
307    /**
308     * Get the margin in pixels between two child items vertically.
309     */
310    public int getVerticalMargin() {
311        return mLayoutManager.getVerticalMargin();
312    }
313
314    /**
315     * Set the margin in pixels between two child items horizontally.
316     */
317    public void setHorizontalMargin(int margin) {
318        mLayoutManager.setHorizontalMargin(margin);
319        requestLayout();
320    }
321
322    /**
323     * Get the margin in pixels between two child items horizontally.
324     */
325    public int getHorizontalMargin() {
326        return mLayoutManager.getHorizontalMargin();
327    }
328
329    /**
330     * Register a callback to be invoked when an item in BaseGridView has
331     * been selected.
332     */
333    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
334        mLayoutManager.setOnChildSelectedListener(listener);
335    }
336
337    /**
338     * Change the selected item immediately without animation.
339     */
340    public void setSelectedPosition(int position) {
341        mLayoutManager.setSelection(this, position);
342    }
343
344    /**
345     * Change the selected item and run an animation to scroll to the target
346     * position.
347     */
348    public void setSelectedPositionSmooth(int position) {
349        mLayoutManager.setSelectionSmooth(this, position);
350    }
351
352    /**
353     * Get the selected item position.
354     */
355    public int getSelectedPosition() {
356        return mLayoutManager.getSelection();
357    }
358
359    /**
360     * Set if an animation should run when a child changes size or when adding
361     * or removing a child.
362     * <p><i>Unstable API, might change later.</i>
363     */
364    public void setAnimateChildLayout(boolean animateChildLayout) {
365        mLayoutManager.setAnimateChildLayout(animateChildLayout);
366    }
367
368    /**
369     * Return true if an animation will run when a child changes size or when
370     * adding or removing a child.
371     * <p><i>Unstable API, might change later.</i>
372     */
373    public boolean isChildLayoutAnimated() {
374        return mLayoutManager.isChildLayoutAnimated();
375    }
376
377    /**
378     * Set an interpolator for the animation when a child changes size or when
379     * adding or removing a child.
380     * <p><i>Unstable API, might change later.</i>
381     */
382    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
383        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
384    }
385
386    /**
387     * Get the interpolator for the animation when a child changes size or when
388     * adding or removing a child.
389     * <p><i>Unstable API, might change later.</i>
390     */
391    public Interpolator getChildLayoutAnimationInterpolator() {
392        return mLayoutManager.getChildLayoutAnimationInterpolator();
393    }
394
395    /**
396     * Set the duration of the animation when a child changes size or when
397     * adding or removing a child.
398     * <p><i>Unstable API, might change later.</i>
399     */
400    public void setChildLayoutAnimationDuration(long duration) {
401        mLayoutManager.setChildLayoutAnimationDuration(duration);
402    }
403
404    /**
405     * Get the duration of the animation when a child changes size or when
406     * adding or removing a child.
407     * <p><i>Unstable API, might change later.</i>
408     */
409    public long getChildLayoutAnimationDuration() {
410        return mLayoutManager.getChildLayoutAnimationDuration();
411    }
412
413    /**
414     * Describes how the child views are positioned. Defaults to
415     * GRAVITY_TOP|GRAVITY_LEFT.
416     *
417     * @param gravity See {@link android.view.Gravity}
418     */
419    public void setGravity(int gravity) {
420        mLayoutManager.setGravity(gravity);
421        requestLayout();
422    }
423
424    @Override
425    protected final void onMeasure(int widthSpec, int heightSpec) {
426        mLayoutManager.gridOnMeasure(widthSpec, heightSpec, mMeasuredSize);
427        setMeasuredDimension(mMeasuredSize[0], mMeasuredSize[1]);
428    }
429
430    @Override
431    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
432        return mLayoutManager.gridOnRequestFocus(this, direction, previouslyFocusedRect);
433    }
434
435    /**
436     * Get the x/y offsets to final position from current position if the view
437     * is selected.
438     *
439     * @param view The view to get offsets.
440     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
441     *        Y.
442     */
443    public void getViewSelectedOffsets(View view, int[] offsets) {
444        mLayoutManager.getViewSelectedOffsets(view, offsets);
445    }
446
447    @Override
448    public int getChildDrawingOrder(int childCount, int i) {
449        return mLayoutManager.getChildDrawingOrder(this, childCount, i);
450    }
451
452    final boolean isChildrenDrawingOrderEnabledInternal() {
453        return isChildrenDrawingOrderEnabled();
454    }
455}
456