HorizontalGridView.java revision 76c3b90228d8c4afc6d24c683e9c95f41ae619c9
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; 32 33/** 34 * A view that shows items in a horizontal scrolling list. The items come from 35 * the {@link RecyclerView.Adapter} associated with this view. 36 */ 37public class HorizontalGridView extends BaseGridView { 38 39 private boolean mFadingLowEdge; 40 private boolean mFadingHighEdge; 41 42 private Paint mTempPaint = new Paint(); 43 private Bitmap mTempBitmapLow; 44 private LinearGradient mLowFadeShader; 45 private int mLowFadeShaderLength; 46 private int mLowFadeShaderOffset; 47 private Bitmap mTempBitmapHigh; 48 private LinearGradient mHighFadeShader; 49 private int mHighFadeShaderLength; 50 private int mHighFadeShaderOffset; 51 private Rect mTempRect = new Rect(); 52 53 public HorizontalGridView(Context context) { 54 this(context, null); 55 } 56 57 public HorizontalGridView(Context context, AttributeSet attrs) { 58 this(context, attrs, 0); 59 } 60 61 public HorizontalGridView(Context context, AttributeSet attrs, int defStyle) { 62 super(context, attrs, defStyle); 63 mLayoutManager.setOrientation(RecyclerView.HORIZONTAL); 64 initAttributes(context, attrs); 65 } 66 67 protected void initAttributes(Context context, AttributeSet attrs) { 68 initBaseGridViewAttributes(context, attrs); 69 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbHorizontalGridView); 70 setRowHeight(a); 71 setNumRows(a.getInt(R.styleable.lbHorizontalGridView_numberOfRows, 1)); 72 a.recycle(); 73 setWillNotDraw(false); 74 mTempPaint = new Paint(); 75 mTempPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); 76 } 77 78 void setRowHeight(TypedArray array) { 79 TypedValue typedValue = array.peekValue(R.styleable.lbHorizontalGridView_rowHeight); 80 int size; 81 if (typedValue != null && typedValue.type == TypedValue.TYPE_DIMENSION) { 82 size = array.getDimensionPixelSize(R.styleable.lbHorizontalGridView_rowHeight, 0); 83 } else { 84 size = array.getInt(R.styleable.lbHorizontalGridView_rowHeight, 0); 85 } 86 setRowHeight(size); 87 } 88 89 /** 90 * Set the number of rows. Defaults to one. 91 */ 92 public void setNumRows(int numRows) { 93 mLayoutManager.setNumRows(numRows); 94 requestLayout(); 95 } 96 97 /** 98 * Set the row height. 99 * 100 * @param height May be WRAP_CONTENT, or a size in pixels. If zero, 101 * row height will be fixed based on number of rows and view height. 102 */ 103 public void setRowHeight(int height) { 104 mLayoutManager.setRowHeight(height); 105 requestLayout(); 106 } 107 108 /** 109 * Set fade out left edge to transparent. Note turn on fading edge is very expensive 110 * that you should turn off when HorizontalGridView is scrolling. 111 */ 112 public final void setFadingLeftEdge(boolean fading) { 113 if (mFadingLowEdge != fading) { 114 mFadingLowEdge = fading; 115 if (!mFadingLowEdge) { 116 mTempBitmapLow = null; 117 } 118 invalidate(); 119 } 120 } 121 122 /** 123 * Return true if fading left edge. 124 */ 125 public final boolean getFadingLeftEdge() { 126 return mFadingLowEdge; 127 } 128 129 /** 130 * Set left edge fading length in pixels. 131 */ 132 public final void setFadingLeftEdgeLength(int fadeLength) { 133 if (mLowFadeShaderLength != fadeLength) { 134 mLowFadeShaderLength = fadeLength; 135 if (mLowFadeShaderLength != 0) { 136 mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0, 137 Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP); 138 } else { 139 mLowFadeShader = null; 140 } 141 invalidate(); 142 } 143 } 144 145 /** 146 * Get left edge fading length in pixels. 147 */ 148 public final int getFadingLeftEdgeLength() { 149 return mLowFadeShaderLength; 150 } 151 152 /** 153 * Set distance in pixels between fading start position and left padding edge. 154 * The fading start position is positive when start position is inside left padding 155 * area. Default value is 0, means that the fading starts from left padding edge. 156 */ 157 public final void setFadingLeftEdgeOffset(int fadeOffset) { 158 if (mLowFadeShaderOffset != fadeOffset) { 159 mLowFadeShaderOffset = fadeOffset; 160 invalidate(); 161 } 162 } 163 164 /** 165 * Get distance in pixels between fading start position and left padding edge. 166 * The fading start position is positive when start position is inside left padding 167 * area. Default value is 0, means that the fading starts from left padding edge. 168 */ 169 public final int getFadingLeftEdgeOffset() { 170 return mLowFadeShaderOffset; 171 } 172 173 /** 174 * Set fade out right edge to transparent. Note turn on fading edge is very expensive 175 * that you should turn off when HorizontalGridView is scrolling. 176 */ 177 public final void setFadingRightEdge(boolean fading) { 178 if (mFadingHighEdge != fading) { 179 mFadingHighEdge = fading; 180 if (!mFadingHighEdge) { 181 mTempBitmapHigh = null; 182 } 183 invalidate(); 184 } 185 } 186 187 /** 188 * Return true if fading right edge. 189 */ 190 public final boolean getFadingRightEdge() { 191 return mFadingHighEdge; 192 } 193 194 /** 195 * Set right edge fading length in pixels. 196 */ 197 public final void setFadingRightEdgeLength(int fadeLength) { 198 if (mHighFadeShaderLength != fadeLength) { 199 mHighFadeShaderLength = fadeLength; 200 if (mHighFadeShaderLength != 0) { 201 mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0, 202 Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP); 203 } else { 204 mHighFadeShader = null; 205 } 206 invalidate(); 207 } 208 } 209 210 /** 211 * Get right edge fading length in pixels. 212 */ 213 public final int getFadingRightEdgeLength() { 214 return mHighFadeShaderLength; 215 } 216 217 /** 218 * Get distance in pixels between fading start position and right padding edge. 219 * The fading start position is positive when start position is inside right padding 220 * area. Default value is 0, means that the fading starts from right padding edge. 221 */ 222 public final void setFadingRightEdgeOffset(int fadeOffset) { 223 if (mHighFadeShaderOffset != fadeOffset) { 224 mHighFadeShaderOffset = fadeOffset; 225 invalidate(); 226 } 227 } 228 229 /** 230 * Set distance in pixels between fading start position and right padding edge. 231 * The fading start position is positive when start position is inside right padding 232 * area. Default value is 0, means that the fading starts from right padding edge. 233 */ 234 public final int getFadingRightEdgeOffset() { 235 return mHighFadeShaderOffset; 236 } 237 238 private boolean needsFadingLowEdge() { 239 if (!mFadingLowEdge) { 240 return false; 241 } 242 final int c = getChildCount(); 243 for (int i = 0; i < c; i++) { 244 View view = getChildAt(i); 245 if (mLayoutManager.getOpticalLeft(view) < 246 getPaddingLeft() - mLowFadeShaderOffset) { 247 return true; 248 } 249 } 250 return false; 251 } 252 253 private boolean needsFadingHighEdge() { 254 if (!mFadingHighEdge) { 255 return false; 256 } 257 final int c = getChildCount(); 258 for (int i = c - 1; i >= 0; i--) { 259 View view = getChildAt(i); 260 if (mLayoutManager.getOpticalRight(view) > getWidth() 261 - getPaddingRight() + mHighFadeShaderOffset) { 262 return true; 263 } 264 } 265 return false; 266 } 267 268 private Bitmap getTempBitmapLow() { 269 if (mTempBitmapLow == null 270 || mTempBitmapLow.getWidth() != mLowFadeShaderLength 271 || mTempBitmapLow.getHeight() != getHeight()) { 272 mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(), 273 Bitmap.Config.ARGB_8888); 274 } 275 return mTempBitmapLow; 276 } 277 278 private Bitmap getTempBitmapHigh() { 279 if (mTempBitmapHigh == null 280 || mTempBitmapHigh.getWidth() != mHighFadeShaderLength 281 || mTempBitmapHigh.getHeight() != getHeight()) { 282 // TODO: fix logic for sharing mTempBitmapLow 283 if (false && 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 mTempBitmapHigh = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(), 290 Bitmap.Config.ARGB_8888); 291 } 292 } 293 return mTempBitmapHigh; 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 + (mFadingLowEdge ? mLowFadeShaderLength : 0), 0, 318 highEdge - (mFadingHighEdge ? mHighFadeShaderLength : 0), 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