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