HorizontalGridView.java revision 46443cb5b092f1d9156342645088eead9da026f6
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 updateLayerType(); 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 if (typedValue != null) { 81 int size = array.getLayoutDimension(R.styleable.lbHorizontalGridView_rowHeight, 0); 82 setRowHeight(size); 83 } 84 } 85 86 /** 87 * Set the number of rows. Defaults to one. 88 */ 89 public void setNumRows(int numRows) { 90 mLayoutManager.setNumRows(numRows); 91 requestLayout(); 92 } 93 94 /** 95 * Set the row height. 96 * 97 * @param height May be WRAP_CONTENT, or a size in pixels. If zero, 98 * row height will be fixed based on number of rows and view height. 99 */ 100 public void setRowHeight(int height) { 101 mLayoutManager.setRowHeight(height); 102 requestLayout(); 103 } 104 105 /** 106 * Set fade out left edge to transparent. Note turn on fading edge is very expensive 107 * that you should turn off when HorizontalGridView is scrolling. 108 */ 109 public final void setFadingLeftEdge(boolean fading) { 110 if (mFadingLowEdge != fading) { 111 mFadingLowEdge = fading; 112 if (!mFadingLowEdge) { 113 mTempBitmapLow = null; 114 } 115 invalidate(); 116 updateLayerType(); 117 } 118 } 119 120 /** 121 * Return true if fading left edge. 122 */ 123 public final boolean getFadingLeftEdge() { 124 return mFadingLowEdge; 125 } 126 127 /** 128 * Set left edge fading length in pixels. 129 */ 130 public final void setFadingLeftEdgeLength(int fadeLength) { 131 if (mLowFadeShaderLength != fadeLength) { 132 mLowFadeShaderLength = fadeLength; 133 if (mLowFadeShaderLength != 0) { 134 mLowFadeShader = new LinearGradient(0, 0, mLowFadeShaderLength, 0, 135 Color.TRANSPARENT, Color.BLACK, Shader.TileMode.CLAMP); 136 } else { 137 mLowFadeShader = null; 138 } 139 invalidate(); 140 } 141 } 142 143 /** 144 * Get left edge fading length in pixels. 145 */ 146 public final int getFadingLeftEdgeLength() { 147 return mLowFadeShaderLength; 148 } 149 150 /** 151 * Set distance in pixels between fading start position and left padding edge. 152 * The fading start position is positive when start position is inside left padding 153 * area. Default value is 0, means that the fading starts from left padding edge. 154 */ 155 public final void setFadingLeftEdgeOffset(int fadeOffset) { 156 if (mLowFadeShaderOffset != fadeOffset) { 157 mLowFadeShaderOffset = fadeOffset; 158 invalidate(); 159 } 160 } 161 162 /** 163 * Get distance in pixels between fading start position and left padding edge. 164 * The fading start position is positive when start position is inside left padding 165 * area. Default value is 0, means that the fading starts from left padding edge. 166 */ 167 public final int getFadingLeftEdgeOffset() { 168 return mLowFadeShaderOffset; 169 } 170 171 /** 172 * Set fade out right edge to transparent. Note turn on fading edge is very expensive 173 * that you should turn off when HorizontalGridView is scrolling. 174 */ 175 public final void setFadingRightEdge(boolean fading) { 176 if (mFadingHighEdge != fading) { 177 mFadingHighEdge = fading; 178 if (!mFadingHighEdge) { 179 mTempBitmapHigh = null; 180 } 181 invalidate(); 182 updateLayerType(); 183 } 184 } 185 186 /** 187 * Return true if fading right edge. 188 */ 189 public final boolean getFadingRightEdge() { 190 return mFadingHighEdge; 191 } 192 193 /** 194 * Set right edge fading length in pixels. 195 */ 196 public final void setFadingRightEdgeLength(int fadeLength) { 197 if (mHighFadeShaderLength != fadeLength) { 198 mHighFadeShaderLength = fadeLength; 199 if (mHighFadeShaderLength != 0) { 200 mHighFadeShader = new LinearGradient(0, 0, mHighFadeShaderLength, 0, 201 Color.BLACK, Color.TRANSPARENT, Shader.TileMode.CLAMP); 202 } else { 203 mHighFadeShader = null; 204 } 205 invalidate(); 206 } 207 } 208 209 /** 210 * Get right edge fading length in pixels. 211 */ 212 public final int getFadingRightEdgeLength() { 213 return mHighFadeShaderLength; 214 } 215 216 /** 217 * Get distance in pixels between fading start position and right padding edge. 218 * The fading start position is positive when start position is inside right padding 219 * area. Default value is 0, means that the fading starts from right padding edge. 220 */ 221 public final void setFadingRightEdgeOffset(int fadeOffset) { 222 if (mHighFadeShaderOffset != fadeOffset) { 223 mHighFadeShaderOffset = fadeOffset; 224 invalidate(); 225 } 226 } 227 228 /** 229 * Set distance in pixels between fading start position and right padding edge. 230 * The fading start position is positive when start position is inside right padding 231 * area. Default value is 0, means that the fading starts from right padding edge. 232 */ 233 public final int getFadingRightEdgeOffset() { 234 return mHighFadeShaderOffset; 235 } 236 237 private boolean needsFadingLowEdge() { 238 if (!mFadingLowEdge) { 239 return false; 240 } 241 final int c = getChildCount(); 242 for (int i = 0; i < c; i++) { 243 View view = getChildAt(i); 244 if (mLayoutManager.getOpticalLeft(view) < 245 getPaddingLeft() - mLowFadeShaderOffset) { 246 return true; 247 } 248 } 249 return false; 250 } 251 252 private boolean needsFadingHighEdge() { 253 if (!mFadingHighEdge) { 254 return false; 255 } 256 final int c = getChildCount(); 257 for (int i = c - 1; i >= 0; i--) { 258 View view = getChildAt(i); 259 if (mLayoutManager.getOpticalRight(view) > getWidth() 260 - getPaddingRight() + mHighFadeShaderOffset) { 261 return true; 262 } 263 } 264 return false; 265 } 266 267 private Bitmap getTempBitmapLow() { 268 if (mTempBitmapLow == null 269 || mTempBitmapLow.getWidth() != mLowFadeShaderLength 270 || mTempBitmapLow.getHeight() != getHeight()) { 271 mTempBitmapLow = Bitmap.createBitmap(mLowFadeShaderLength, getHeight(), 272 Bitmap.Config.ARGB_8888); 273 } 274 return mTempBitmapLow; 275 } 276 277 private Bitmap getTempBitmapHigh() { 278 if (mTempBitmapHigh == null 279 || mTempBitmapHigh.getWidth() != mHighFadeShaderLength 280 || mTempBitmapHigh.getHeight() != getHeight()) { 281 // TODO: fix logic for sharing mTempBitmapLow 282 if (false && mTempBitmapLow != null 283 && mTempBitmapLow.getWidth() == mHighFadeShaderLength 284 && mTempBitmapLow.getHeight() == getHeight()) { 285 // share same bitmap for low edge fading and high edge fading. 286 mTempBitmapHigh = mTempBitmapLow; 287 } else { 288 mTempBitmapHigh = Bitmap.createBitmap(mHighFadeShaderLength, getHeight(), 289 Bitmap.Config.ARGB_8888); 290 } 291 } 292 return mTempBitmapHigh; 293 } 294 295 @Override 296 public void draw(Canvas canvas) { 297 final boolean needsFadingLow = needsFadingLowEdge(); 298 final boolean needsFadingHigh = needsFadingHighEdge(); 299 if (!needsFadingLow) { 300 mTempBitmapLow = null; 301 } 302 if (!needsFadingHigh) { 303 mTempBitmapHigh = null; 304 } 305 if (!needsFadingLow && !needsFadingHigh) { 306 super.draw(canvas); 307 return; 308 } 309 310 int lowEdge = mFadingLowEdge? getPaddingLeft() - mLowFadeShaderOffset - mLowFadeShaderLength : 0; 311 int highEdge = mFadingHighEdge ? getWidth() - getPaddingRight() 312 + mHighFadeShaderOffset + mHighFadeShaderLength : getWidth(); 313 314 // draw not-fade content 315 int save = canvas.save(); 316 canvas.clipRect(lowEdge + (mFadingLowEdge ? mLowFadeShaderLength : 0), 0, 317 highEdge - (mFadingHighEdge ? mHighFadeShaderLength : 0), getHeight()); 318 super.draw(canvas); 319 canvas.restoreToCount(save); 320 321 Canvas tmpCanvas = new Canvas(); 322 mTempRect.top = 0; 323 mTempRect.bottom = getHeight(); 324 if (needsFadingLow && mLowFadeShaderLength > 0) { 325 Bitmap tempBitmap = getTempBitmapLow(); 326 tempBitmap.eraseColor(Color.TRANSPARENT); 327 tmpCanvas.setBitmap(tempBitmap); 328 // draw original content 329 int tmpSave = tmpCanvas.save(); 330 tmpCanvas.clipRect(0, 0, mLowFadeShaderLength, getHeight()); 331 tmpCanvas.translate(-lowEdge, 0); 332 super.draw(tmpCanvas); 333 tmpCanvas.restoreToCount(tmpSave); 334 // draw fading out 335 mTempPaint.setShader(mLowFadeShader); 336 tmpCanvas.drawRect(0, 0, mLowFadeShaderLength, getHeight(), mTempPaint); 337 // copy back to canvas 338 mTempRect.left = 0; 339 mTempRect.right = mLowFadeShaderLength; 340 canvas.translate(lowEdge, 0); 341 canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null); 342 canvas.translate(-lowEdge, 0); 343 } 344 if (needsFadingHigh && mHighFadeShaderLength > 0) { 345 Bitmap tempBitmap = getTempBitmapHigh(); 346 tempBitmap.eraseColor(Color.TRANSPARENT); 347 tmpCanvas.setBitmap(tempBitmap); 348 // draw original content 349 int tmpSave = tmpCanvas.save(); 350 tmpCanvas.clipRect(0, 0, mHighFadeShaderLength, getHeight()); 351 tmpCanvas.translate(-(highEdge - mHighFadeShaderLength), 0); 352 super.draw(tmpCanvas); 353 tmpCanvas.restoreToCount(tmpSave); 354 // draw fading out 355 mTempPaint.setShader(mHighFadeShader); 356 tmpCanvas.drawRect(0, 0, mHighFadeShaderLength, getHeight(), mTempPaint); 357 // copy back to canvas 358 mTempRect.left = 0; 359 mTempRect.right = mHighFadeShaderLength; 360 canvas.translate(highEdge - mHighFadeShaderLength, 0); 361 canvas.drawBitmap(tempBitmap, mTempRect, mTempRect, null); 362 canvas.translate(-(highEdge - mHighFadeShaderLength), 0); 363 } 364 } 365 366 /** 367 * Updates the layer type for this view. 368 * If fading edges are needed, use a hardware layer. This works around the problem 369 * that when a child invalidates itself (for example has an animated background), 370 * the parent view must also be invalidated to refresh the display list which 371 * updates the the caching bitmaps used to draw the fading edges. 372 */ 373 private void updateLayerType() { 374 if (mFadingLowEdge || mFadingHighEdge) { 375 setLayerType(View.LAYER_TYPE_HARDWARE, null); 376 setWillNotDraw(false); 377 } else { 378 setLayerType(View.LAYER_TYPE_NONE, null); 379 setWillNotDraw(true); 380 } 381 } 382} 383