HorizontalGridView.java revision f272f7533fcb5aba341e9ab2f4ff0421d668a8ca
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.Bitmap;
19import android.graphics.Canvas;
20import android.graphics.Color;
21import android.graphics.LinearGradient;
22import android.graphics.Paint;
23import android.graphics.PorterDuff;
24import android.graphics.PorterDuffXfermode;
25import android.graphics.Rect;
26import android.graphics.Shader;
27import android.support.v17.leanback.R;
28import android.support.v7.widget.RecyclerView;
29import android.util.AttributeSet;
30import android.util.TypedValue;
31import android.view.View;
32import android.view.ViewGroup;
33
34/**
35 * A view that shows items in a horizontal scrolling list. The items come from
36 * the {@link RecyclerView.Adapter} associated with this view.
37 */
38public class HorizontalGridView extends BaseGridView {
39
40    private boolean mFadingLowEdge;
41    private boolean mFadingHighEdge;
42
43    private Paint mTempPaint = new Paint();
44    private Bitmap mTempBitmapLow;
45    private LinearGradient mLowFadeShader;
46    private int mLowFadeShaderLength;
47    private int mLowFadeShaderOffset;
48    private Bitmap mTempBitmapHigh;
49    private LinearGradient mHighFadeShader;
50    private int mHighFadeShaderLength;
51    private int mHighFadeShaderOffset;
52    private Rect mTempRect = new Rect();
53
54    public HorizontalGridView(Context context) {
55        this(context, null);
56    }
57
58    public HorizontalGridView(Context context, AttributeSet attrs) {
59        this(context, attrs, 0);
60    }
61
62    public HorizontalGridView(Context context, AttributeSet attrs, int defStyle) {
63        super(context, attrs, defStyle);
64        mLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
65        initAttributes(context, attrs);
66    }
67
68    protected void initAttributes(Context context, AttributeSet attrs) {
69        initBaseGridViewAttributes(context, attrs);
70        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView);
71        setRowHeight(a);
72        setNumRows(a.getInt(R.styleable.lbHorizontalGridView_numberOfRows, 1));
73        a.recycle();
74        setWillNotDraw(false);
75        mTempPaint = new Paint();
76        mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
77    }
78
79    void setRowHeight(TypedArray array) {
80        TypedValue typedValue = array.peekValue(R.styleable.lbHorizontalGridView_rowHeight);
81        int size;
82        if (typedValue != null && typedValue.type == TypedValue.TYPE_DIMENSION) {
83            size = array.getDimensionPixelSize(R.styleable.lbHorizontalGridView_rowHeight, 0);
84        } else {
85            size = array.getInt(R.styleable.lbHorizontalGridView_rowHeight, 0);
86        }
87        setRowHeight(size);
88    }
89
90    /**
91     * Set the number of rows.  Defaults to one.
92     */
93    public void setNumRows(int numRows) {
94        mLayoutManager.setNumRows(numRows);
95        requestLayout();
96    }
97
98    /**
99     * Set the row height.
100     *
101     * @param height May be WRAP_CONTENT, or a size in pixels. If zero,
102     * row height will be fixed based on number of rows and view height.
103     */
104    public void setRowHeight(int height) {
105        mLayoutManager.setRowHeight(height);
106        requestLayout();
107    }
108
109    /**
110     * Set fade out left edge to transparent.   Note turn on fading edge is very expensive
111     * that you should turn off when HorizontalGridView is scrolling.
112     */
113    public final void setFadingLeftEdge(boolean fading) {
114        if (mFadingLowEdge != fading) {
115            mFadingLowEdge = fading;
116            if (!mFadingLowEdge) {
117                mTempBitmapLow = null;
118            }
119            invalidate();
120        }
121    }
122
123    /**
124     * Return true if fading left edge.
125     */
126    public final boolean getFadingLeftEdge() {
127        return mFadingLowEdge;
128    }
129
130    /**
131     * Set left edge fading length in pixels.
132     */
133    public final void setFadingLeftEdgeLength(int fadeLength) {
134        if (mLowFadeShaderLength != fadeLength) {
135            mLowFadeShaderLength = fadeLength;
136            if (mLowFadeShaderLength != 0) {
137                mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0,
138                        Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP);
139            } else {
140                mLowFadeShader = null;
141            }
142            invalidate();
143        }
144    }
145
146    /**
147     * Get left edge fading length in pixels.
148     */
149    public final int getFadingLeftEdgeLength() {
150        return mLowFadeShaderLength;
151    }
152
153    /**
154     * Set distance in pixels between fading start position and left padding edge.
155     * The fading start position is positive when start position is inside left padding
156     * area.  Default value is 0, means that the fading starts from left padding edge.
157     */
158    public final void setFadingLeftEdgeOffset(int fadeOffset) {
159        if (mLowFadeShaderOffset != fadeOffset) {
160            mLowFadeShaderOffset = fadeOffset;
161            invalidate();
162        }
163    }
164
165    /**
166     * Get distance in pixels between fading start position and left padding edge.
167     * The fading start position is positive when start position is inside left padding
168     * area.  Default value is 0, means that the fading starts from left padding edge.
169     */
170    public final int getFadingLeftEdgeOffset() {
171        return mLowFadeShaderOffset;
172    }
173
174    /**
175     * Set fade out right edge to transparent.   Note turn on fading edge is very expensive
176     * that you should turn off when HorizontalGridView is scrolling.
177     */
178    public final void setFadingRightEdge(boolean fading) {
179        if (mFadingHighEdge != fading) {
180            mFadingHighEdge = fading;
181            if (!mFadingHighEdge) {
182                mTempBitmapHigh = null;
183            }
184            invalidate();
185        }
186    }
187
188    /**
189     * Return true if fading right edge.
190     */
191    public final boolean getFadingRightEdge() {
192        return mFadingHighEdge;
193    }
194
195    /**
196     * Set right edge fading length in pixels.
197     */
198    public final void setFadingRightEdgeLength(int fadeLength) {
199        if (mHighFadeShaderLength != fadeLength) {
200            mHighFadeShaderLength = fadeLength;
201            if (mHighFadeShaderLength != 0) {
202                mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0,
203                        Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP);
204            } else {
205                mHighFadeShader = null;
206            }
207            invalidate();
208        }
209    }
210
211    /**
212     * Get right edge fading length in pixels.
213     */
214    public final int getFadingRightEdgeLength() {
215        return mHighFadeShaderLength;
216    }
217
218    /**
219     * Get distance in pixels between fading start position and right padding edge.
220     * The fading start position is positive when start position is inside right padding
221     * area.  Default value is 0, means that the fading starts from right padding edge.
222     */
223    public final void setFadingRightEdgeOffset(int fadeOffset) {
224        if (mHighFadeShaderOffset != fadeOffset) {
225            mHighFadeShaderOffset = fadeOffset;
226            invalidate();
227        }
228    }
229
230    /**
231     * Set distance in pixels between fading start position and right padding edge.
232     * The fading start position is positive when start position is inside right padding
233     * area.  Default value is 0, means that the fading starts from right padding edge.
234     */
235    public final int getFadingRightEdgeOffset() {
236        return mHighFadeShaderOffset;
237    }
238
239    private boolean needsFadingLowEdge() {
240        if (!mFadingLowEdge) {
241            return false;
242        }
243        final int c = getChildCount();
244        for (int i = 0; i < c; i++) {
245            View view = getChildAt(i);
246            if (mLayoutManager.getOpticalLeft(view) <
247                    getPaddingLeft() - mLowFadeShaderOffset) {
248                return true;
249            }
250        }
251        return false;
252    }
253
254    private boolean needsFadingHighEdge() {
255        if (!mFadingHighEdge) {
256            return false;
257        }
258        final int c = getChildCount();
259        for (int i = c - 1; i >= 0; i--) {
260            View view = getChildAt(i);
261            if (mLayoutManager.getOpticalRight(view) > getWidth()
262                    - getPaddingRight() + mHighFadeShaderOffset) {
263                return true;
264            }
265        }
266        return false;
267    }
268
269    private Bitmap getTempBitmapLow() {
270        if (mTempBitmapLow == null
271                || mTempBitmapLow.getWidth() != mLowFadeShaderLength
272                || mTempBitmapLow.getHeight() != getHeight()) {
273            mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(),
274                    Bitmap.Config.ARGB_8888);
275        }
276        return mTempBitmapLow;
277    }
278
279    private Bitmap getTempBitmapHigh() {
280        if (mTempBitmapHigh == null
281                || mTempBitmapHigh.getWidth() != mHighFadeShaderLength
282                || mTempBitmapHigh.getHeight() != getHeight()) {
283            if (mTempBitmapLow != null
284                    && mTempBitmapLow.getWidth() == mHighFadeShaderLength
285                    && mTempBitmapLow.getHeight() == getHeight()) {
286                // share same bitmap for low edge fading and high edge fading.
287                mTempBitmapHigh = mTempBitmapLow;
288            } else {
289                mTempBitmapLow = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(),
290                        Bitmap.Config.ARGB_8888);
291            }
292        }
293        return mTempBitmapLow;
294    }
295
296    @Override
297    public void draw(Canvas canvas) {
298        final boolean needsFadingLow = needsFadingLowEdge();
299        final boolean needsFadingHigh = needsFadingHighEdge();
300        if (!needsFadingLow) {
301            mTempBitmapLow = null;
302        }
303        if (!needsFadingHigh) {
304            mTempBitmapHigh = null;
305        }
306        if (!needsFadingLow && !needsFadingHigh) {
307            super.draw(canvas);
308            return;
309        }
310
311        int lowEdge = mFadingLowEdge? getPaddingLeft() - mLowFadeShaderOffset - mLowFadeShaderLength : 0;
312        int highEdge = mFadingHighEdge ? getWidth() - getPaddingRight()
313                + mHighFadeShaderOffset + mHighFadeShaderLength : getWidth();
314
315        // draw not-fade content
316        int save = canvas.save();
317        canvas.clipRect(lowEdge + mLowFadeShaderLength, 0,
318                highEdge - mHighFadeShaderLength, getHeight());
319        super.draw(canvas);
320        canvas.restoreToCount(save);
321
322        Canvas tmpCanvas = new Canvas();
323        mTempRect.top = 0;
324        mTempRect.bottom = getHeight();
325        if (needsFadingLow && mLowFadeShaderLength > 0) {
326            Bitmap tempBitmap = getTempBitmapLow();
327            tempBitmap.eraseColor(Color.TRANSPARENT);
328            tmpCanvas.setBitmap(tempBitmap);
329            // draw original content
330            int tmpSave = tmpCanvas.save();
331            tmpCanvas.clipRect(0, 0, mLowFadeShaderLength, getHeight());
332            tmpCanvas.translate(-lowEdge, 0);
333            super.draw(tmpCanvas);
334            tmpCanvas.restoreToCount(tmpSave);
335            // draw fading out
336            mTempPaint.setShader(mLowFadeShader);
337            tmpCanvas.drawRect(0, 0, mLowFadeShaderLength, getHeight(), mTempPaint);
338            // copy back to canvas
339            mTempRect.left = 0;
340            mTempRect.right = mLowFadeShaderLength;
341            canvas.translate(lowEdge, 0);
342            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
343            canvas.translate(-lowEdge, 0);
344        }
345        if (needsFadingHigh && mHighFadeShaderLength > 0) {
346            Bitmap tempBitmap = getTempBitmapHigh();
347            tempBitmap.eraseColor(Color.TRANSPARENT);
348            tmpCanvas.setBitmap(tempBitmap);
349            // draw original content
350            int tmpSave = tmpCanvas.save();
351            tmpCanvas.clipRect(0, 0, mHighFadeShaderLength, getHeight());
352            tmpCanvas.translate(-(highEdge - mHighFadeShaderLength), 0);
353            super.draw(tmpCanvas);
354            tmpCanvas.restoreToCount(tmpSave);
355            // draw fading out
356            mTempPaint.setShader(mHighFadeShader);
357            tmpCanvas.drawRect(0, 0, mHighFadeShaderLength, getHeight(), mTempPaint);
358            // copy back to canvas
359            mTempRect.left = 0;
360            mTempRect.right = mHighFadeShaderLength;
361            canvas.translate(highEdge - mHighFadeShaderLength, 0);
362            canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null);
363            canvas.translate(-(highEdge - mHighFadeShaderLength), 0);
364        }
365    }
366}
367