1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package android.support.v17.leanback.graphics; 17 18 import android.support.annotation.RequiresApi; 19 import android.graphics.Bitmap; 20 import android.graphics.Canvas; 21 import android.graphics.ColorFilter; 22 import android.graphics.Paint; 23 import android.graphics.PixelFormat; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.os.Build; 27 import android.support.annotation.NonNull; 28 import android.util.IntProperty; 29 import android.util.Property; 30 31 /** 32 * Subclass of {@link Drawable} that can be used to draw a bitmap into a region. Bitmap 33 * will be scaled to fit the full width of the region and will be aligned to the top left corner. 34 * Any region outside the bounds will be clipped during {@link #draw(Canvas)} call. Top 35 * position of the bitmap can be controlled by {@link #setVerticalOffset(int)} call or 36 * {@link #PROPERTY_VERTICAL_OFFSET}. 37 */ 38 public class FitWidthBitmapDrawable extends Drawable { 39 40 static class BitmapState extends Drawable.ConstantState { 41 Paint mPaint; 42 Bitmap mBitmap; 43 Rect mSource; 44 final Rect mDefaultSource = new Rect(); 45 int mOffset; 46 BitmapState()47 BitmapState() { 48 mPaint = new Paint(); 49 } 50 BitmapState(BitmapState other)51 BitmapState(BitmapState other) { 52 mBitmap = other.mBitmap; 53 mPaint = new Paint(other.mPaint); 54 mSource = other.mSource != null ? new Rect(other.mSource) : null; 55 mDefaultSource.set(other.mDefaultSource); 56 mOffset = other.mOffset; 57 } 58 59 @NonNull 60 @Override newDrawable()61 public Drawable newDrawable() { 62 return new FitWidthBitmapDrawable(this); 63 } 64 65 @Override getChangingConfigurations()66 public int getChangingConfigurations() { 67 return 0; 68 } 69 } 70 71 final Rect mDest = new Rect(); 72 BitmapState mBitmapState; 73 boolean mMutated = false; 74 FitWidthBitmapDrawable()75 public FitWidthBitmapDrawable() { 76 mBitmapState = new BitmapState(); 77 } 78 FitWidthBitmapDrawable(BitmapState state)79 FitWidthBitmapDrawable(BitmapState state) { 80 mBitmapState = state; 81 } 82 83 @Override getConstantState()84 public ConstantState getConstantState() { 85 return mBitmapState; 86 } 87 88 @Override mutate()89 public Drawable mutate() { 90 if (!mMutated && super.mutate() == this) { 91 mBitmapState = new BitmapState(mBitmapState); 92 mMutated = true; 93 } 94 return this; 95 } 96 97 /** 98 * Sets the bitmap. 99 */ setBitmap(Bitmap bitmap)100 public void setBitmap(Bitmap bitmap) { 101 mBitmapState.mBitmap = bitmap; 102 if (bitmap != null) { 103 mBitmapState.mDefaultSource.set(0, 0, bitmap.getWidth(), bitmap.getHeight()); 104 } else { 105 mBitmapState.mDefaultSource.set(0, 0, 0, 0); 106 } 107 mBitmapState.mSource = null; 108 } 109 110 /** 111 * Returns the bitmap. 112 */ getBitmap()113 public Bitmap getBitmap() { 114 return mBitmapState.mBitmap; 115 } 116 117 /** 118 * Sets the {@link Rect} used for extracting the bitmap. 119 */ setSource(Rect source)120 public void setSource(Rect source) { 121 mBitmapState.mSource = source; 122 } 123 124 /** 125 * Returns the {@link Rect} used for extracting the bitmap. 126 */ getSource()127 public Rect getSource() { 128 return mBitmapState.mSource; 129 } 130 131 /** 132 * Sets the vertical offset which will be used for drawing the bitmap. The bitmap drawing 133 * will start the provided vertical offset. 134 * @see #PROPERTY_VERTICAL_OFFSET 135 */ setVerticalOffset(int offset)136 public void setVerticalOffset(int offset) { 137 mBitmapState.mOffset = offset; 138 invalidateSelf(); 139 } 140 141 /** 142 * Returns the current vertical offset. 143 * @see #PROPERTY_VERTICAL_OFFSET 144 */ getVerticalOffset()145 public int getVerticalOffset() { 146 return mBitmapState.mOffset; 147 } 148 149 @Override draw(Canvas canvas)150 public void draw(Canvas canvas) { 151 if (mBitmapState.mBitmap != null) { 152 Rect bounds = getBounds(); 153 mDest.left = 0; 154 mDest.top = mBitmapState.mOffset; 155 mDest.right = bounds.width(); 156 157 Rect source = validateSource(); 158 float scale = (float) bounds.width() / source.width(); 159 mDest.bottom = mDest.top + (int) (source.height() * scale); 160 int i = canvas.save(); 161 canvas.clipRect(bounds); 162 canvas.drawBitmap(mBitmapState.mBitmap, source, mDest, mBitmapState.mPaint); 163 canvas.restoreToCount(i); 164 } 165 } 166 167 @Override setAlpha(int alpha)168 public void setAlpha(int alpha) { 169 final int oldAlpha = mBitmapState.mPaint.getAlpha(); 170 if (alpha != oldAlpha) { 171 mBitmapState.mPaint.setAlpha(alpha); 172 invalidateSelf(); 173 } 174 } 175 176 /** 177 * @return Alpha value between 0(inclusive) and 255(inclusive) 178 */ 179 @Override getAlpha()180 public int getAlpha() { 181 return mBitmapState.mPaint.getAlpha(); 182 } 183 184 @Override setColorFilter(ColorFilter colorFilter)185 public void setColorFilter(ColorFilter colorFilter) { 186 mBitmapState.mPaint.setColorFilter(colorFilter); 187 invalidateSelf(); 188 } 189 190 @Override getOpacity()191 public int getOpacity() { 192 final Bitmap bitmap = mBitmapState.mBitmap; 193 return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) 194 ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 195 } 196 validateSource()197 private Rect validateSource() { 198 if (mBitmapState.mSource == null) { 199 return mBitmapState.mDefaultSource; 200 } else { 201 return mBitmapState.mSource; 202 } 203 } 204 205 /** 206 * Property for {@link #setVerticalOffset(int)} and {@link #getVerticalOffset()}. 207 */ 208 public static final Property<FitWidthBitmapDrawable, Integer> PROPERTY_VERTICAL_OFFSET; 209 210 static { 211 if (Build.VERSION.SDK_INT >= 24) { 212 // use IntProperty 213 PROPERTY_VERTICAL_OFFSET = getVerticalOffsetIntProperty(); 214 } else { 215 // use Property 216 PROPERTY_VERTICAL_OFFSET = new Property<FitWidthBitmapDrawable, Integer>(Integer.class, 217 "verticalOffset") { 218 @Override 219 public void set(FitWidthBitmapDrawable object, Integer value) { 220 object.setVerticalOffset(value); 221 } 222 223 @Override 224 public Integer get(FitWidthBitmapDrawable object) { 225 return object.getVerticalOffset(); 226 } 227 }; 228 } 229 } 230 231 @RequiresApi(24) getVerticalOffsetIntProperty()232 static IntProperty<FitWidthBitmapDrawable> getVerticalOffsetIntProperty() { 233 return new IntProperty<FitWidthBitmapDrawable>("verticalOffset") { 234 @Override 235 public void setValue(FitWidthBitmapDrawable fitWidthBitmapDrawable, int value) { 236 fitWidthBitmapDrawable.setVerticalOffset(value); 237 } 238 239 @Override 240 public Integer get(FitWidthBitmapDrawable fitWidthBitmapDrawable) { 241 return fitWidthBitmapDrawable.getVerticalOffset(); 242 } 243 }; 244 } 245 } 246