BaseGridView.java revision e34cae48707e70442aca13e1b4ab55757292828d
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    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        a.recycle();
126    }
127
128    /**
129     * Set the strategy used to scroll in response to item focus changing:
130     * <ul>
131     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
132     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
133     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
134     * </ul>
135     */
136    public void setFocusScrollStrategy(int scrollStrategy) {
137        if (scrollStrategy != FOCUS_SCROLL_ALIGNED && scrollStrategy != FOCUS_SCROLL_ITEM
138            && scrollStrategy != FOCUS_SCROLL_PAGE) {
139            throw new IllegalArgumentException("Invalid scrollStrategy");
140        }
141        mLayoutManager.setFocusScrollStrategy(scrollStrategy);
142        requestLayout();
143    }
144
145    /**
146     * Returns the strategy used to scroll in response to item focus changing.
147     * <ul>
148     * <li>{@link #FOCUS_SCROLL_ALIGNED} (default) </li>
149     * <li>{@link #FOCUS_SCROLL_ITEM}</li>
150     * <li>{@link #FOCUS_SCROLL_PAGE}</li>
151     * </ul>
152     */
153    public int getFocusScrollStrategy() {
154        return mLayoutManager.getFocusScrollStrategy();
155    }
156
157    /**
158     * Set how the focused item gets aligned in the view.
159     *
160     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
161     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
162     *        {@link #WINDOW_ALIGN_NO_EDGE}.
163     */
164    public void setWindowAlignment(int windowAlignment) {
165        mLayoutManager.setWindowAlignment(windowAlignment);
166        requestLayout();
167    }
168
169    /**
170     * Get how the focused item gets aligned in the view.
171     *
172     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
173     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
174     */
175    public int getWindowAlignment() {
176        return mLayoutManager.getWindowAlignment();
177    }
178
179    /**
180     * Set the absolute offset in pixels for window alignment.
181     *
182     * @param offset The number of pixels to offset. Can be negative for
183     *        alignment from the high edge, or positive for alignment from the
184     *        low edge.
185     */
186    public void setWindowAlignmentOffset(int offset) {
187        mLayoutManager.setWindowAlignmentOffset(offset);
188        requestLayout();
189    }
190
191    /**
192     * Get the absolute offset in pixels for window alignment.
193     *
194     * @return The number of pixels to offset. Will be negative for alignment
195     *         from the high edge, or positive for alignment from the low edge.
196     *         Default value is 0.
197     */
198    public int getWindowAlignmentOffset() {
199        return mLayoutManager.getWindowAlignmentOffset();
200    }
201
202    /**
203     * Set offset percent for window alignment in addition to {@link
204     * #getWindowAlignmentOffset()}.
205     *
206     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
207     *        width from low edge. Use
208     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
209     */
210    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
211        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
212        requestLayout();
213    }
214
215    /**
216     * Get offset percent for window alignment in addition to
217     * {@link #getWindowAlignmentOffset()}.
218     *
219     * @return Percentage to offset. E.g., 40 means 40% of the width from the
220     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
221     *         disabled. Default value is 50.
222     */
223    public float getWindowAlignmentOffsetPercent() {
224        return mLayoutManager.getWindowAlignmentOffsetPercent();
225    }
226
227    /**
228     * Set the absolute offset in pixels for item alignment.
229     *
230     * @param offset The number of pixels to offset. Can be negative for
231     *        alignment from the high edge, or positive for alignment from the
232     *        low edge.
233     */
234    public void setItemAlignmentOffset(int offset) {
235        mLayoutManager.setItemAlignmentOffset(offset);
236        requestLayout();
237    }
238
239    /**
240     * Get the absolute offset in pixels for item alignment.
241     *
242     * @return The number of pixels to offset. Will be negative for alignment
243     *         from the high edge, or positive for alignment from the low edge.
244     *         Default value is 0.
245     */
246    public int getItemAlignmentOffset() {
247        return mLayoutManager.getItemAlignmentOffset();
248    }
249
250    /**
251     * Set offset percent for item alignment in addition to {@link
252     * #getItemAlignmentOffset()}.
253     *
254     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
255     *        width from the low edge. Use
256     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
257     */
258    public void setItemAlignmentOffsetPercent(float offsetPercent) {
259        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
260        requestLayout();
261    }
262
263    /**
264     * Get offset percent for item alignment in addition to {@link
265     * #getItemAlignmentOffset()}.
266     *
267     * @return Percentage to offset. E.g., 40 means 40% of the width from the
268     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
269     *         disabled. Default value is 50.
270     */
271    public float getItemAlignmentOffsetPercent() {
272        return mLayoutManager.getItemAlignmentOffsetPercent();
273    }
274
275    /**
276     * Set the id of the view to align with. Use zero (default) for the item
277     * view itself.
278     */
279    public void setItemAlignmentViewId(int viewId) {
280        mLayoutManager.setItemAlignmentViewId(viewId);
281    }
282
283    /**
284     * Get the id of the view to align with, or zero for the item view itself.
285     */
286    public int getItemAlignmentViewId() {
287        return mLayoutManager.getItemAlignmentViewId();
288    }
289
290    /**
291     * Set the margin in pixels between two child items.
292     */
293    public void setItemMargin(int margin) {
294        mLayoutManager.setItemMargin(margin);
295        requestLayout();
296    }
297
298    /**
299     * Set the margin in pixels between two child items vertically.
300     */
301    public void setVerticalMargin(int margin) {
302        mLayoutManager.setVerticalMargin(margin);
303        requestLayout();
304    }
305
306    /**
307     * Get the margin in pixels between two child items vertically.
308     */
309    public int getVerticalMargin() {
310        return mLayoutManager.getVerticalMargin();
311    }
312
313    /**
314     * Set the margin in pixels between two child items horizontally.
315     */
316    public void setHorizontalMargin(int margin) {
317        mLayoutManager.setHorizontalMargin(margin);
318        requestLayout();
319    }
320
321    /**
322     * Get the margin in pixels between two child items horizontally.
323     */
324    public int getHorizontalMargin() {
325        return mLayoutManager.getHorizontalMargin();
326    }
327
328    /**
329     * Register a callback to be invoked when an item in BaseGridView has
330     * been selected.
331     */
332    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
333        mLayoutManager.setOnChildSelectedListener(listener);
334    }
335
336    /**
337     * Change the selected item immediately without animation.
338     */
339    public void setSelectedPosition(int position) {
340        mLayoutManager.setSelection(this, position);
341    }
342
343    /**
344     * Change the selected item and run an animation to scroll to the target
345     * position.
346     */
347    public void setSelectedPositionSmooth(int position) {
348        mLayoutManager.setSelectionSmooth(this, position);
349    }
350
351    /**
352     * Get the selected item position.
353     */
354    public int getSelectedPosition() {
355        return mLayoutManager.getSelection();
356    }
357
358    /**
359     * Set if an animation should run when a child changes size or when adding
360     * or removing a child.
361     * <p><i>Unstable API, might change later.</i>
362     */
363    public void setAnimateChildLayout(boolean animateChildLayout) {
364        mLayoutManager.setAnimateChildLayout(animateChildLayout);
365    }
366
367    /**
368     * Return true if an animation will run when a child changes size or when
369     * adding or removing a child.
370     * <p><i>Unstable API, might change later.</i>
371     */
372    public boolean isChildLayoutAnimated() {
373        return mLayoutManager.isChildLayoutAnimated();
374    }
375
376    /**
377     * Set an interpolator for the animation when a child changes size or when
378     * adding or removing a child.
379     * <p><i>Unstable API, might change later.</i>
380     */
381    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
382        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
383    }
384
385    /**
386     * Get the interpolator for the animation when a child changes size or when
387     * adding or removing a child.
388     * <p><i>Unstable API, might change later.</i>
389     */
390    public Interpolator getChildLayoutAnimationInterpolator() {
391        return mLayoutManager.getChildLayoutAnimationInterpolator();
392    }
393
394    /**
395     * Set the duration of the animation when a child changes size or when
396     * adding or removing a child.
397     * <p><i>Unstable API, might change later.</i>
398     */
399    public void setChildLayoutAnimationDuration(long duration) {
400        mLayoutManager.setChildLayoutAnimationDuration(duration);
401    }
402
403    /**
404     * Get the duration of the animation when a child changes size or when
405     * adding or removing a child.
406     * <p><i>Unstable API, might change later.</i>
407     */
408    public long getChildLayoutAnimationDuration() {
409        return mLayoutManager.getChildLayoutAnimationDuration();
410    }
411
412    /**
413     * Describes how the child views are positioned. Defaults to
414     * GRAVITY_TOP|GRAVITY_LEFT.
415     *
416     * @param gravity See {@link android.view.Gravity}
417     */
418    public void setGravity(int gravity) {
419        mLayoutManager.setGravity(gravity);
420        requestLayout();
421    }
422
423    @Override
424    public void setDescendantFocusability (int focusability) {
425        // enforce FOCUS_AFTER_DESCENDANTS
426        super.setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
427    }
428
429    @Override
430    public boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
431        return mLayoutManager.gridOnRequestFocusInDescendants(this, direction,
432                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    /**
457     * Disable or enable focus search.
458     */
459    public final void setFocusSearchDisabled(boolean disabled) {
460        mLayoutManager.setFocusSearchDisabled(disabled);
461    }
462
463    /**
464     * Return true if focus search is disabled.
465     */
466    public final boolean isFocusSearchDisabled() {
467        return mLayoutManager.isFocusSearchDisabled();
468    }
469
470    /**
471     * Get if view has same row sibling next to it.
472     *
473     * @param position Position in adapter.
474     */
475    public boolean hasNextViewInSameRow(int position) {
476        return mLayoutManager.hasNextViewInSameRow(position);
477    }
478
479    /**
480     * Get if view has same row sibling in front of it.
481     *
482     * @param position Position in adapter.
483     */
484    public boolean hasPreviousViewInSameRow(int position) {
485        return mLayoutManager.hasPreviousViewInSameRow(position);
486    }
487}
488