BaseGridView.java revision 5a2782ae17df5331a594fe03d5d89251a8b9f6d4
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     * The first item is aligned with the low edge of the viewport. When
35     * navigating away from the first item, the focus maintains a middle
36     * location.
37     * <p>
38     * The middle location is calculated by "windowAlignOffset" and
39     * "windowAlignOffsetPercent"; if neither of these two is defined, the
40     * default value is 1/2 of the size.
41     */
42    public final static int WINDOW_ALIGN_LOW_EDGE = 1;
43
44    /**
45     * The last item is aligned with the high edge of the viewport when
46     * navigating to the end of list. When navigating away from the end, the
47     * focus maintains a middle location.
48     * <p>
49     * The middle location is calculated by "windowAlignOffset" and
50     * "windowAlignOffsetPercent"; if neither of these two is defined, the
51     * default value is 1/2 of the size.
52     */
53    public final static int WINDOW_ALIGN_HIGH_EDGE = 1 << 1;
54
55    /**
56     * The first item and last item are aligned with the two edges of the
57     * viewport. When navigating in the middle of list, the focus maintains a
58     * middle location.
59     * <p>
60     * The middle location is calculated by "windowAlignOffset" and
61     * "windowAlignOffsetPercent"; if neither of these two is defined, the
62     * default value is 1/2 of the size.
63     */
64    public final static int WINDOW_ALIGN_BOTH_EDGE =
65            WINDOW_ALIGN_LOW_EDGE | WINDOW_ALIGN_HIGH_EDGE;
66
67    /**
68     * The focused item always stays in a middle location.
69     * <p>
70     * The middle location is calculated by "windowAlignOffset" and
71     * "windowAlignOffsetPercent"; if neither of these two is defined, the
72     * default value is 1/2 of the size.
73     */
74    public final static int WINDOW_ALIGN_NO_EDGE = 0;
75
76    /**
77     * Value indicates that percent is not used.
78     */
79    public final static float WINDOW_ALIGN_OFFSET_PERCENT_DISABLED = -1;
80
81    /**
82     * Value indicates that percent is not used.
83     */
84    public final static float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1;
85
86    protected final GridLayoutManager mLayoutManager;
87
88    private int[] mMeasuredSize = new int[2];
89
90    public BaseGridView(Context context, AttributeSet attrs, int defStyle) {
91        super(context, attrs, defStyle);
92        mLayoutManager = new GridLayoutManager(this);
93        setLayoutManager(mLayoutManager);
94        setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
95        setHasFixedSize(true);
96    }
97
98    protected void initBaseGridViewAttributes(Context context, AttributeSet attrs) {
99        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseGridView);
100        boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
101        boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
102        mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
103        mLayoutManager.setVerticalMargin(
104                a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
105        mLayoutManager.setHorizontalMargin(
106                a.getDimensionPixelSize(R.styleable.lbBaseGridView_horizontalMargin, 0));
107        a.recycle();
108    }
109
110    /**
111     * Set how the focused item gets aligned in the view.
112     *
113     * @param windowAlignment {@link #WINDOW_ALIGN_BOTH_EDGE},
114     *        {@link #WINDOW_ALIGN_LOW_EDGE}, {@link #WINDOW_ALIGN_HIGH_EDGE} or
115     *        {@link #WINDOW_ALIGN_NO_EDGE}.
116     */
117    public void setWindowAlignment(int windowAlignment) {
118        mLayoutManager.setWindowAlignment(windowAlignment);
119        requestLayout();
120    }
121
122    /**
123     * Get how the focused item gets aligned in the view.
124     *
125     * @return {@link #WINDOW_ALIGN_BOTH_EDGE}, {@link #WINDOW_ALIGN_LOW_EDGE},
126     *         {@link #WINDOW_ALIGN_HIGH_EDGE} or {@link #WINDOW_ALIGN_NO_EDGE}.
127     */
128    public int getWindowAlignment() {
129        return mLayoutManager.getWindowAlignment();
130    }
131
132    /**
133     * Set the absolute offset in pixels for window alignment.
134     *
135     * @param offset The number of pixels to offset. Can be negative for
136     *        alignment from the high edge, or positive for alignment from the
137     *        low edge.
138     */
139    public void setWindowAlignmentOffset(int offset) {
140        mLayoutManager.setWindowAlignmentOffset(offset);
141        requestLayout();
142    }
143
144    /**
145     * Get the absolute offset in pixels for window alignment.
146     *
147     * @return The number of pixels to offset. Will be negative for alignment
148     *         from the high edge, or positive for alignment from the low edge.
149     *         Default value is 0.
150     */
151    public int getWindowAlignmentOffset() {
152        return mLayoutManager.getWindowAlignmentOffset();
153    }
154
155    /**
156     * Set offset percent for window alignment in addition to {@link
157     * #getWindowAlignmentOffset()}.
158     *
159     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
160     *        width from low edge. Use
161     *        {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
162     */
163    public void setWindowAlignmentOffsetPercent(float offsetPercent) {
164        mLayoutManager.setWindowAlignmentOffsetPercent(offsetPercent);
165        requestLayout();
166    }
167
168    /**
169     * Get offset percent for window alignment in addition to
170     * {@link #getWindowAlignmentOffset()}.
171     *
172     * @return Percentage to offset. E.g., 40 means 40% of the width from the
173     *         low edge, or {@link #WINDOW_ALIGN_OFFSET_PERCENT_DISABLED} if
174     *         disabled. Default value is 50.
175     */
176    public float getWindowAlignmentOffsetPercent() {
177        return mLayoutManager.getWindowAlignmentOffsetPercent();
178    }
179
180    /**
181     * Set the absolute offset in pixels for item 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 setItemAlignmentOffset(int offset) {
188        mLayoutManager.setItemAlignmentOffset(offset);
189        requestLayout();
190    }
191
192    /**
193     * Get the absolute offset in pixels for item 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 getItemAlignmentOffset() {
200        return mLayoutManager.getItemAlignmentOffset();
201    }
202
203    /**
204     * Set offset percent for item alignment in addition to {@link
205     * #getItemAlignmentOffset()}.
206     *
207     * @param offsetPercent Percentage to offset. E.g., 40 means 40% of the
208     *        width from the low edge. Use
209     *        {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} to disable.
210     */
211    public void setItemAlignmentOffsetPercent(float offsetPercent) {
212        mLayoutManager.setItemAlignmentOffsetPercent(offsetPercent);
213        requestLayout();
214    }
215
216    /**
217     * Get offset percent for item alignment in addition to {@link
218     * #getItemAlignmentOffset()}.
219     *
220     * @return Percentage to offset. E.g., 40 means 40% of the width from the
221     *         low edge, or {@link #ITEM_ALIGN_OFFSET_PERCENT_DISABLED} if
222     *         disabled. Default value is 50.
223     */
224    public float getItemAlignmentOffsetPercent() {
225        return mLayoutManager.getItemAlignmentOffsetPercent();
226    }
227
228    /**
229     * Set the id of the view to align with. Use zero (default) for the item
230     * view itself.
231     */
232    public void setItemAlignmentViewId(int viewId) {
233        mLayoutManager.setItemAlignmentViewId(viewId);
234    }
235
236    /**
237     * Get the id of the view to align with, or zero for the item view itself.
238     */
239    public int getItemAlignmentViewId() {
240        return mLayoutManager.getItemAlignmentViewId();
241    }
242
243    /**
244     * Set the margin in pixels between two child items.
245     */
246    public void setItemMargin(int margin) {
247        mLayoutManager.setItemMargin(margin);
248        requestLayout();
249    }
250
251    /**
252     * Set the margin in pixels between two child items vertically.
253     */
254    public void setVerticalMargin(int margin) {
255        mLayoutManager.setVerticalMargin(margin);
256        requestLayout();
257    }
258
259    /**
260     * Get the margin in pixels between two child items vertically.
261     */
262    public int getVerticalMargin() {
263        return mLayoutManager.getVerticalMargin();
264    }
265
266    /**
267     * Set the margin in pixels between two child items horizontally.
268     */
269    public void setHorizontalMargin(int margin) {
270        mLayoutManager.setHorizontalMargin(margin);
271        requestLayout();
272    }
273
274    /**
275     * Get the margin in pixels between two child items horizontally.
276     */
277    public int getHorizontalMargin() {
278        return mLayoutManager.getHorizontalMargin();
279    }
280
281    /**
282     * Register a callback to be invoked when an item in BaseGridView has
283     * been selected.
284     */
285    public void setOnChildSelectedListener(OnChildSelectedListener listener) {
286        mLayoutManager.setOnChildSelectedListener(listener);
287    }
288
289    /**
290     * Change the selected item immediately without animation.
291     */
292    public void setSelectedPosition(int position) {
293        mLayoutManager.setSelection(this, position);
294    }
295
296    /**
297     * Change the selected item and run an animation to scroll to the target
298     * position.
299     */
300    public void setSelectedPositionSmooth(int position) {
301        mLayoutManager.setSelectionSmooth(this, position);
302    }
303
304    /**
305     * Get the selected item position.
306     */
307    public int getSelectedPosition() {
308        return mLayoutManager.getSelection();
309    }
310
311    /**
312     * Set if an animation should run when a child changes size or when adding
313     * or removing a child.
314     * <p><i>Unstable API, might change later.</i>
315     */
316    public void setAnimateChildLayout(boolean animateChildLayout) {
317        mLayoutManager.setAnimateChildLayout(animateChildLayout);
318    }
319
320    /**
321     * Return true if an animation will run when a child changes size or when
322     * adding or removing a child.
323     * <p><i>Unstable API, might change later.</i>
324     */
325    public boolean isChildLayoutAnimated() {
326        return mLayoutManager.isChildLayoutAnimated();
327    }
328
329    /**
330     * Set an interpolator for the animation when a child changes size or when
331     * adding or removing a child.
332     * <p><i>Unstable API, might change later.</i>
333     */
334    public void setChildLayoutAnimationInterpolator(Interpolator interpolator) {
335        mLayoutManager.setChildLayoutAnimationInterpolator(interpolator);
336    }
337
338    /**
339     * Get the interpolator for the animation when a child changes size or when
340     * adding or removing a child.
341     * <p><i>Unstable API, might change later.</i>
342     */
343    public Interpolator getChildLayoutAnimationInterpolator() {
344        return mLayoutManager.getChildLayoutAnimationInterpolator();
345    }
346
347    /**
348     * Set the duration of the animation when a child changes size or when
349     * adding or removing a child.
350     * <p><i>Unstable API, might change later.</i>
351     */
352    public void setChildLayoutAnimationDuration(long duration) {
353        mLayoutManager.setChildLayoutAnimationDuration(duration);
354    }
355
356    /**
357     * Get the duration of the animation when a child changes size or when
358     * adding or removing a child.
359     * <p><i>Unstable API, might change later.</i>
360     */
361    public long getChildLayoutAnimationDuration() {
362        return mLayoutManager.getChildLayoutAnimationDuration();
363    }
364
365    /**
366     * Describes how the child views are positioned. Defaults to
367     * GRAVITY_TOP|GRAVITY_LEFT.
368     *
369     * @param gravity See {@link android.view.Gravity}
370     */
371    public void setGravity(int gravity) {
372        mLayoutManager.setGravity(gravity);
373        requestLayout();
374    }
375
376    @Override
377    protected final void onMeasure(int widthSpec, int heightSpec) {
378        mLayoutManager.onMeasure(widthSpec, heightSpec, mMeasuredSize);
379        setMeasuredDimension(mMeasuredSize[0], mMeasuredSize[1]);
380    }
381
382    @Override
383    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
384        if (mLayoutManager.focusSelectedChild(direction, previouslyFocusedRect)) {
385            return true;
386        }
387        return super.requestFocus(direction, previouslyFocusedRect);
388    }
389
390    /**
391     * Get the x/y offsets to final position from current position if the view
392     * is selected.
393     *
394     * @param view The view to get offsets.
395     * @param offsets offsets[0] holds offset of X, offsets[1] holds offset of
396     *        Y.
397     */
398    public void getViewSelectedOffsets(View view, int[] offsets) {
399        mLayoutManager.getViewSelectedOffsets(view, offsets);
400    }
401}
402