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