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