BitmapDrawable.java revision 7dcdfd7988d1e57c7a705a2d7294e8bfa2b8afc9
1/* 2 * Copyright (C) 2006 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 17package android.graphics.drawable; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.graphics.Canvas; 24import android.graphics.ColorFilter; 25import android.graphics.Paint; 26import android.graphics.PixelFormat; 27import android.graphics.Rect; 28import android.graphics.Shader; 29import android.graphics.BitmapShader; 30import android.util.AttributeSet; 31import android.util.DisplayMetrics; 32import android.view.Gravity; 33 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36 37import java.io.IOException; 38 39/** 40 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 41 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 42 * a {@link android.graphics.Bitmap} object. 43 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 44 * information, see the guide to <a 45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 46 * <p> 47 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 48 * transformation of raw bitmap graphics, and should be used when drawing to a 49 * {@link android.graphics.Canvas}. 50 * </p> 51 * 52 * @attr ref android.R.styleable#BitmapDrawable_src 53 * @attr ref android.R.styleable#BitmapDrawable_antialias 54 * @attr ref android.R.styleable#BitmapDrawable_filter 55 * @attr ref android.R.styleable#BitmapDrawable_dither 56 * @attr ref android.R.styleable#BitmapDrawable_gravity 57 * @attr ref android.R.styleable#BitmapDrawable_tileMode 58 */ 59public class BitmapDrawable extends Drawable { 60 61 private static final int DEFAULT_PAINT_FLAGS = 62 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 63 private BitmapState mBitmapState; 64 private Bitmap mBitmap; 65 private int mTargetDensity; 66 67 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 68 69 private boolean mApplyGravity; 70 private boolean mRebuildShader; 71 private boolean mMutated; 72 73 // These are scaled to match the target density. 74 private int mBitmapWidth; 75 private int mBitmapHeight; 76 77 /** 78 * Create an empty drawable, not dealing with density. 79 * @deprecated Use {@link #BitmapDrawable(Resources)} to ensure 80 * that the drawable has correctly set its target density. 81 */ 82 @Deprecated 83 public BitmapDrawable() { 84 mBitmapState = new BitmapState((Bitmap) null); 85 } 86 87 /** 88 * Create an empty drawable, setting initial target density based on 89 * the display metrics of the resources. 90 */ 91 public BitmapDrawable(Resources res) { 92 mBitmapState = new BitmapState((Bitmap) null); 93 mBitmapState.mTargetDensity = mTargetDensity; 94 } 95 96 /** 97 * Create drawable from a bitmap, not dealing with density. 98 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 99 * that the drawable has correctly set its target density. 100 */ 101 @Deprecated 102 public BitmapDrawable(Bitmap bitmap) { 103 this(new BitmapState(bitmap), null); 104 } 105 106 /** 107 * Create drawable from a bitmap, setting initial target density based on 108 * the display metrics of the resources. 109 */ 110 public BitmapDrawable(Resources res, Bitmap bitmap) { 111 this(new BitmapState(bitmap), res); 112 mBitmapState.mTargetDensity = mTargetDensity; 113 } 114 115 /** 116 * Create a drawable by opening a given file path and decoding the bitmap. 117 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 118 * that the drawable has correctly set its target density. 119 */ 120 @Deprecated 121 public BitmapDrawable(String filepath) { 122 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 123 if (mBitmap == null) { 124 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 125 } 126 } 127 128 /** 129 * Create a drawable by opening a given file path and decoding the bitmap. 130 */ 131 public BitmapDrawable(Resources res, String filepath) { 132 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 133 mBitmapState.mTargetDensity = mTargetDensity; 134 if (mBitmap == null) { 135 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 136 } 137 } 138 139 /** 140 * Create a drawable by decoding a bitmap from the given input stream. 141 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 142 * that the drawable has correctly set its target density. 143 */ 144 @Deprecated 145 public BitmapDrawable(java.io.InputStream is) { 146 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 147 if (mBitmap == null) { 148 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 149 } 150 } 151 152 /** 153 * Create a drawable by decoding a bitmap from the given input stream. 154 */ 155 public BitmapDrawable(Resources res, java.io.InputStream is) { 156 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 157 mBitmapState.mTargetDensity = mTargetDensity; 158 if (mBitmap == null) { 159 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 160 } 161 } 162 163 public final Paint getPaint() { 164 return mBitmapState.mPaint; 165 } 166 167 public final Bitmap getBitmap() { 168 return mBitmap; 169 } 170 171 private void computeBitmapSize() { 172 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 173 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 174 } 175 176 private void setBitmap(Bitmap bitmap) { 177 if (bitmap != mBitmap) { 178 mBitmap = bitmap; 179 if (bitmap != null) { 180 computeBitmapSize(); 181 } else { 182 mBitmapWidth = mBitmapHeight = -1; 183 } 184 invalidateSelf(); 185 } 186 } 187 188 /** 189 * Set the density scale at which this drawable will be rendered. This 190 * method assumes the drawable will be rendered at the same density as the 191 * specified canvas. 192 * 193 * @param canvas The Canvas from which the density scale must be obtained. 194 * 195 * @see android.graphics.Bitmap#setDensity(int) 196 * @see android.graphics.Bitmap#getDensity() 197 */ 198 public void setTargetDensity(Canvas canvas) { 199 setTargetDensity(canvas.getDensity()); 200 } 201 202 /** 203 * Set the density scale at which this drawable will be rendered. 204 * 205 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 206 * 207 * @see android.graphics.Bitmap#setDensity(int) 208 * @see android.graphics.Bitmap#getDensity() 209 */ 210 public void setTargetDensity(DisplayMetrics metrics) { 211 setTargetDensity(metrics.densityDpi); 212 } 213 214 /** 215 * Set the density at which this drawable will be rendered. 216 * 217 * @param density The density scale for this drawable. 218 * 219 * @see android.graphics.Bitmap#setDensity(int) 220 * @see android.graphics.Bitmap#getDensity() 221 */ 222 public void setTargetDensity(int density) { 223 if (mTargetDensity != density) { 224 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 225 if (mBitmap != null) { 226 computeBitmapSize(); 227 } 228 invalidateSelf(); 229 } 230 } 231 232 /** Get the gravity used to position/stretch the bitmap within its bounds. 233 * See android.view.Gravity 234 * @return the gravity applied to the bitmap 235 */ 236 public int getGravity() { 237 return mBitmapState.mGravity; 238 } 239 240 /** Set the gravity used to position/stretch the bitmap within its bounds. 241 See android.view.Gravity 242 * @param gravity the gravity 243 */ 244 public void setGravity(int gravity) { 245 if (mBitmapState.mGravity != gravity) { 246 mBitmapState.mGravity = gravity; 247 mApplyGravity = true; 248 invalidateSelf(); 249 } 250 } 251 252 public void setAntiAlias(boolean aa) { 253 mBitmapState.mPaint.setAntiAlias(aa); 254 invalidateSelf(); 255 } 256 257 @Override 258 public void setFilterBitmap(boolean filter) { 259 mBitmapState.mPaint.setFilterBitmap(filter); 260 invalidateSelf(); 261 } 262 263 @Override 264 public void setDither(boolean dither) { 265 mBitmapState.mPaint.setDither(dither); 266 invalidateSelf(); 267 } 268 269 public Shader.TileMode getTileModeX() { 270 return mBitmapState.mTileModeX; 271 } 272 273 public Shader.TileMode getTileModeY() { 274 return mBitmapState.mTileModeY; 275 } 276 277 public void setTileModeX(Shader.TileMode mode) { 278 setTileModeXY(mode, mBitmapState.mTileModeY); 279 } 280 281 public final void setTileModeY(Shader.TileMode mode) { 282 setTileModeXY(mBitmapState.mTileModeX, mode); 283 } 284 285 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 286 final BitmapState state = mBitmapState; 287 if (state.mPaint.getShader() == null || 288 state.mTileModeX != xmode || state.mTileModeY != ymode) { 289 state.mTileModeX = xmode; 290 state.mTileModeY = ymode; 291 mRebuildShader = true; 292 invalidateSelf(); 293 } 294 } 295 296 @Override 297 public int getChangingConfigurations() { 298 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 299 } 300 301 @Override 302 protected void onBoundsChange(Rect bounds) { 303 super.onBoundsChange(bounds); 304 mApplyGravity = true; 305 } 306 307 @Override 308 public void draw(Canvas canvas) { 309 Bitmap bitmap = mBitmap; 310 if (bitmap != null) { 311 final BitmapState state = mBitmapState; 312 if (mRebuildShader) { 313 Shader.TileMode tmx = state.mTileModeX; 314 Shader.TileMode tmy = state.mTileModeY; 315 316 if (tmx == null && tmy == null) { 317 state.mPaint.setShader(null); 318 } else { 319 Shader s = new BitmapShader(bitmap, 320 tmx == null ? Shader.TileMode.CLAMP : tmx, 321 tmy == null ? Shader.TileMode.CLAMP : tmy); 322 state.mPaint.setShader(s); 323 } 324 mRebuildShader = false; 325 copyBounds(mDstRect); 326 } 327 328 Shader shader = state.mPaint.getShader(); 329 if (shader == null) { 330 if (mApplyGravity) { 331 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 332 getBounds(), mDstRect); 333 mApplyGravity = false; 334 } 335 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 336 } else { 337 if (mApplyGravity) { 338 mDstRect.set(getBounds()); 339 mApplyGravity = false; 340 } 341 canvas.drawRect(mDstRect, state.mPaint); 342 } 343 } 344 } 345 346 @Override 347 public void setAlpha(int alpha) { 348 mBitmapState.mPaint.setAlpha(alpha); 349 invalidateSelf(); 350 } 351 352 @Override 353 public void setColorFilter(ColorFilter cf) { 354 mBitmapState.mPaint.setColorFilter(cf); 355 invalidateSelf(); 356 } 357 358 /** 359 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 360 * that comes from the same resource. 361 * 362 * @return This drawable. 363 */ 364 @Override 365 public Drawable mutate() { 366 if (!mMutated && super.mutate() == this) { 367 mBitmapState = new BitmapState(mBitmapState); 368 mMutated = true; 369 } 370 return this; 371 } 372 373 @Override 374 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 375 throws XmlPullParserException, IOException { 376 super.inflate(r, parser, attrs); 377 378 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 379 380 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 381 if (id == 0) { 382 throw new XmlPullParserException(parser.getPositionDescription() + 383 ": <bitmap> requires a valid src attribute"); 384 } 385 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 386 if (bitmap == null) { 387 throw new XmlPullParserException(parser.getPositionDescription() + 388 ": <bitmap> requires a valid src attribute"); 389 } 390 mBitmapState.mBitmap = bitmap; 391 setBitmap(bitmap); 392 setTargetDensity(r.getDisplayMetrics()); 393 394 final Paint paint = mBitmapState.mPaint; 395 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 396 paint.isAntiAlias())); 397 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 398 paint.isFilterBitmap())); 399 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 400 paint.isDither())); 401 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 402 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 403 if (tileMode != -1) { 404 switch (tileMode) { 405 case 0: 406 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 407 break; 408 case 1: 409 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 410 break; 411 case 2: 412 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 413 break; 414 } 415 } 416 417 a.recycle(); 418 } 419 420 @Override 421 public int getIntrinsicWidth() { 422 return mBitmapWidth; 423 } 424 425 @Override 426 public int getIntrinsicHeight() { 427 return mBitmapHeight; 428 } 429 430 @Override 431 public int getOpacity() { 432 if (mBitmapState.mGravity != Gravity.FILL) { 433 return PixelFormat.TRANSLUCENT; 434 } 435 Bitmap bm = mBitmap; 436 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 437 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 438 } 439 440 @Override 441 public final ConstantState getConstantState() { 442 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 443 return mBitmapState; 444 } 445 446 final static class BitmapState extends ConstantState { 447 Bitmap mBitmap; 448 int mChangingConfigurations; 449 int mGravity = Gravity.FILL; 450 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 451 Shader.TileMode mTileModeX; 452 Shader.TileMode mTileModeY; 453 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 454 455 BitmapState(Bitmap bitmap) { 456 mBitmap = bitmap; 457 } 458 459 BitmapState(BitmapState bitmapState) { 460 this(bitmapState.mBitmap); 461 mChangingConfigurations = bitmapState.mChangingConfigurations; 462 mGravity = bitmapState.mGravity; 463 mTileModeX = bitmapState.mTileModeX; 464 mTileModeY = bitmapState.mTileModeY; 465 mTargetDensity = bitmapState.mTargetDensity; 466 mPaint = new Paint(bitmapState.mPaint); 467 } 468 469 @Override 470 public Drawable newDrawable() { 471 return new BitmapDrawable(this, null); 472 } 473 474 @Override 475 public Drawable newDrawable(Resources res) { 476 return new BitmapDrawable(this, res); 477 } 478 479 @Override 480 public int getChangingConfigurations() { 481 return mChangingConfigurations; 482 } 483 } 484 485 private BitmapDrawable(BitmapState state, Resources res) { 486 mBitmapState = state; 487 if (res != null) { 488 mTargetDensity = res.getDisplayMetrics().densityDpi; 489 } else { 490 mTargetDensity = state.mTargetDensity; 491 } 492 setBitmap(state.mBitmap); 493 } 494} 495