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