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