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 17 package android.graphics.drawable; 18 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.graphics.Bitmap; 22 import android.graphics.BitmapFactory; 23 import android.graphics.BitmapShader; 24 import android.graphics.Canvas; 25 import android.graphics.ColorFilter; 26 import android.graphics.Matrix; 27 import android.graphics.Paint; 28 import android.graphics.PixelFormat; 29 import android.graphics.Rect; 30 import android.graphics.Shader; 31 import android.graphics.Xfermode; 32 import android.util.AttributeSet; 33 import android.util.DisplayMetrics; 34 import android.util.LayoutDirection; 35 import android.view.Gravity; 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.IOException; 40 41 /** 42 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 43 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 44 * a {@link android.graphics.Bitmap} object. 45 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 46 * information, see the guide to <a 47 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 48 * <p> 49 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 50 * transformation of raw bitmap graphics, and should be used when drawing to a 51 * {@link android.graphics.Canvas}. 52 * </p> 53 * 54 * @attr ref android.R.styleable#BitmapDrawable_src 55 * @attr ref android.R.styleable#BitmapDrawable_antialias 56 * @attr ref android.R.styleable#BitmapDrawable_filter 57 * @attr ref android.R.styleable#BitmapDrawable_dither 58 * @attr ref android.R.styleable#BitmapDrawable_gravity 59 * @attr ref android.R.styleable#BitmapDrawable_mipMap 60 * @attr ref android.R.styleable#BitmapDrawable_tileMode 61 */ 62 public class BitmapDrawable extends Drawable { 63 64 private static final int DEFAULT_PAINT_FLAGS = 65 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 66 private BitmapState mBitmapState; 67 private Bitmap mBitmap; 68 private int mTargetDensity; 69 70 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 71 72 private boolean mApplyGravity; 73 private boolean mMutated; 74 75 // These are scaled to match the target density. 76 private int mBitmapWidth; 77 private int mBitmapHeight; 78 79 // Mirroring matrix for using with Shaders 80 private Matrix mMirrorMatrix; 81 82 /** 83 * Create an empty drawable, not dealing with density. 84 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 85 * instead to specify a bitmap to draw with and ensure the correct density is set. 86 */ 87 @Deprecated 88 public BitmapDrawable() { 89 mBitmapState = new BitmapState((Bitmap) null); 90 } 91 92 /** 93 * Create an empty drawable, setting initial target density based on 94 * the display metrics of the resources. 95 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 96 * instead to specify a bitmap to draw with. 97 */ 98 @Deprecated 99 @SuppressWarnings({"UnusedParameters"}) 100 public BitmapDrawable(Resources res) { 101 mBitmapState = new BitmapState((Bitmap) null); 102 mBitmapState.mTargetDensity = mTargetDensity; 103 } 104 105 /** 106 * Create drawable from a bitmap, not dealing with density. 107 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 108 * that the drawable has correctly set its target density. 109 */ 110 @Deprecated 111 public BitmapDrawable(Bitmap bitmap) { 112 this(new BitmapState(bitmap), null); 113 } 114 115 /** 116 * Create drawable from a bitmap, setting initial target density based on 117 * the display metrics of the resources. 118 */ 119 public BitmapDrawable(Resources res, Bitmap bitmap) { 120 this(new BitmapState(bitmap), res); 121 mBitmapState.mTargetDensity = mTargetDensity; 122 } 123 124 /** 125 * Create a drawable by opening a given file path and decoding the bitmap. 126 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 127 * that the drawable has correctly set its target density. 128 */ 129 @Deprecated 130 public BitmapDrawable(String filepath) { 131 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 132 if (mBitmap == null) { 133 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 134 } 135 } 136 137 /** 138 * Create a drawable by opening a given file path and decoding the bitmap. 139 */ 140 @SuppressWarnings({"UnusedParameters"}) 141 public BitmapDrawable(Resources res, String filepath) { 142 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 143 mBitmapState.mTargetDensity = mTargetDensity; 144 if (mBitmap == null) { 145 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 146 } 147 } 148 149 /** 150 * Create a drawable by decoding a bitmap from the given input stream. 151 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 152 * that the drawable has correctly set its target density. 153 */ 154 @Deprecated 155 public BitmapDrawable(java.io.InputStream is) { 156 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 157 if (mBitmap == null) { 158 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 159 } 160 } 161 162 /** 163 * Create a drawable by decoding a bitmap from the given input stream. 164 */ 165 @SuppressWarnings({"UnusedParameters"}) 166 public BitmapDrawable(Resources res, java.io.InputStream is) { 167 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 168 mBitmapState.mTargetDensity = mTargetDensity; 169 if (mBitmap == null) { 170 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 171 } 172 } 173 174 /** 175 * Returns the paint used to render this drawable. 176 */ 177 public final Paint getPaint() { 178 return mBitmapState.mPaint; 179 } 180 181 /** 182 * Returns the bitmap used by this drawable to render. May be null. 183 */ 184 public final Bitmap getBitmap() { 185 return mBitmap; 186 } 187 188 private void computeBitmapSize() { 189 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 190 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 191 } 192 193 private void setBitmap(Bitmap bitmap) { 194 if (bitmap != mBitmap) { 195 mBitmap = bitmap; 196 if (bitmap != null) { 197 computeBitmapSize(); 198 } else { 199 mBitmapWidth = mBitmapHeight = -1; 200 } 201 invalidateSelf(); 202 } 203 } 204 205 /** 206 * Set the density scale at which this drawable will be rendered. This 207 * method assumes the drawable will be rendered at the same density as the 208 * specified canvas. 209 * 210 * @param canvas The Canvas from which the density scale must be obtained. 211 * 212 * @see android.graphics.Bitmap#setDensity(int) 213 * @see android.graphics.Bitmap#getDensity() 214 */ 215 public void setTargetDensity(Canvas canvas) { 216 setTargetDensity(canvas.getDensity()); 217 } 218 219 /** 220 * Set the density scale at which this drawable will be rendered. 221 * 222 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 223 * 224 * @see android.graphics.Bitmap#setDensity(int) 225 * @see android.graphics.Bitmap#getDensity() 226 */ 227 public void setTargetDensity(DisplayMetrics metrics) { 228 setTargetDensity(metrics.densityDpi); 229 } 230 231 /** 232 * Set the density at which this drawable will be rendered. 233 * 234 * @param density The density scale for this drawable. 235 * 236 * @see android.graphics.Bitmap#setDensity(int) 237 * @see android.graphics.Bitmap#getDensity() 238 */ 239 public void setTargetDensity(int density) { 240 if (mTargetDensity != density) { 241 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 242 if (mBitmap != null) { 243 computeBitmapSize(); 244 } 245 invalidateSelf(); 246 } 247 } 248 249 /** Get the gravity used to position/stretch the bitmap within its bounds. 250 * See android.view.Gravity 251 * @return the gravity applied to the bitmap 252 */ 253 public int getGravity() { 254 return mBitmapState.mGravity; 255 } 256 257 /** Set the gravity used to position/stretch the bitmap within its bounds. 258 See android.view.Gravity 259 * @param gravity the gravity 260 */ 261 public void setGravity(int gravity) { 262 if (mBitmapState.mGravity != gravity) { 263 mBitmapState.mGravity = gravity; 264 mApplyGravity = true; 265 invalidateSelf(); 266 } 267 } 268 269 /** 270 * Enables or disables the mipmap hint for this drawable's bitmap. 271 * See {@link Bitmap#setHasMipMap(boolean)} for more information. 272 * 273 * If the bitmap is null calling this method has no effect. 274 * 275 * @param mipMap True if the bitmap should use mipmaps, false otherwise. 276 * 277 * @see #hasMipMap() 278 */ 279 public void setMipMap(boolean mipMap) { 280 if (mBitmapState.mBitmap != null) { 281 mBitmapState.mBitmap.setHasMipMap(mipMap); 282 invalidateSelf(); 283 } 284 } 285 286 /** 287 * Indicates whether the mipmap hint is enabled on this drawable's bitmap. 288 * 289 * @return True if the mipmap hint is set, false otherwise. If the bitmap 290 * is null, this method always returns false. 291 * 292 * @see #setMipMap(boolean) 293 * @attr ref android.R.styleable#BitmapDrawable_mipMap 294 */ 295 public boolean hasMipMap() { 296 return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap(); 297 } 298 299 /** 300 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 301 * the edges of the bitmap only so it applies only when the drawable is rotated. 302 * 303 * @param aa True if the bitmap should be anti-aliased, false otherwise. 304 * 305 * @see #hasAntiAlias() 306 */ 307 public void setAntiAlias(boolean aa) { 308 mBitmapState.mPaint.setAntiAlias(aa); 309 invalidateSelf(); 310 } 311 312 /** 313 * Indicates whether anti-aliasing is enabled for this drawable. 314 * 315 * @return True if anti-aliasing is enabled, false otherwise. 316 * 317 * @see #setAntiAlias(boolean) 318 */ 319 public boolean hasAntiAlias() { 320 return mBitmapState.mPaint.isAntiAlias(); 321 } 322 323 @Override 324 public void setFilterBitmap(boolean filter) { 325 mBitmapState.mPaint.setFilterBitmap(filter); 326 invalidateSelf(); 327 } 328 329 @Override 330 public void setDither(boolean dither) { 331 mBitmapState.mPaint.setDither(dither); 332 invalidateSelf(); 333 } 334 335 /** 336 * Indicates the repeat behavior of this drawable on the X axis. 337 * 338 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 339 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 340 */ 341 public Shader.TileMode getTileModeX() { 342 return mBitmapState.mTileModeX; 343 } 344 345 /** 346 * Indicates the repeat behavior of this drawable on the Y axis. 347 * 348 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 349 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 350 */ 351 public Shader.TileMode getTileModeY() { 352 return mBitmapState.mTileModeY; 353 } 354 355 /** 356 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 357 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 358 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 359 * is smaller than this drawable. 360 * 361 * @param mode The repeat mode for this drawable. 362 * 363 * @see #setTileModeY(android.graphics.Shader.TileMode) 364 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 365 */ 366 public void setTileModeX(Shader.TileMode mode) { 367 setTileModeXY(mode, mBitmapState.mTileModeY); 368 } 369 370 /** 371 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 372 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 373 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 374 * is smaller than this drawable. 375 * 376 * @param mode The repeat mode for this drawable. 377 * 378 * @see #setTileModeX(android.graphics.Shader.TileMode) 379 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 380 */ 381 public final void setTileModeY(Shader.TileMode mode) { 382 setTileModeXY(mBitmapState.mTileModeX, mode); 383 } 384 385 /** 386 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 387 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 388 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 389 * is smaller than this drawable. 390 * 391 * @param xmode The X repeat mode for this drawable. 392 * @param ymode The Y repeat mode for this drawable. 393 * 394 * @see #setTileModeX(android.graphics.Shader.TileMode) 395 * @see #setTileModeY(android.graphics.Shader.TileMode) 396 */ 397 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 398 final BitmapState state = mBitmapState; 399 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 400 state.mTileModeX = xmode; 401 state.mTileModeY = ymode; 402 state.mRebuildShader = true; 403 invalidateSelf(); 404 } 405 } 406 407 @Override 408 public void setAutoMirrored(boolean mirrored) { 409 if (mBitmapState.mAutoMirrored != mirrored) { 410 mBitmapState.mAutoMirrored = mirrored; 411 invalidateSelf(); 412 } 413 } 414 415 @Override 416 public final boolean isAutoMirrored() { 417 return mBitmapState.mAutoMirrored; 418 } 419 420 @Override 421 public int getChangingConfigurations() { 422 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 423 } 424 425 private boolean needMirroring() { 426 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 427 } 428 429 private void updateMirrorMatrix(float dx) { 430 if (mMirrorMatrix == null) { 431 mMirrorMatrix = new Matrix(); 432 } 433 mMirrorMatrix.setTranslate(dx, 0); 434 mMirrorMatrix.preScale(-1.0f, 1.0f); 435 } 436 437 @Override 438 protected void onBoundsChange(Rect bounds) { 439 super.onBoundsChange(bounds); 440 mApplyGravity = true; 441 Shader shader = mBitmapState.mPaint.getShader(); 442 if (shader != null) { 443 if (needMirroring()) { 444 updateMirrorMatrix(bounds.right - bounds.left); 445 shader.setLocalMatrix(mMirrorMatrix); 446 } else { 447 if (mMirrorMatrix != null) { 448 mMirrorMatrix = null; 449 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 450 } 451 } 452 } 453 } 454 455 @Override 456 public void draw(Canvas canvas) { 457 Bitmap bitmap = mBitmap; 458 if (bitmap != null) { 459 final BitmapState state = mBitmapState; 460 if (state.mRebuildShader) { 461 Shader.TileMode tmx = state.mTileModeX; 462 Shader.TileMode tmy = state.mTileModeY; 463 464 if (tmx == null && tmy == null) { 465 state.mPaint.setShader(null); 466 } else { 467 state.mPaint.setShader(new BitmapShader(bitmap, 468 tmx == null ? Shader.TileMode.CLAMP : tmx, 469 tmy == null ? Shader.TileMode.CLAMP : tmy)); 470 } 471 state.mRebuildShader = false; 472 copyBounds(mDstRect); 473 } 474 475 Shader shader = state.mPaint.getShader(); 476 final boolean needMirroring = needMirroring(); 477 if (shader == null) { 478 if (mApplyGravity) { 479 final int layoutDirection = getLayoutDirection(); 480 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 481 getBounds(), mDstRect, layoutDirection); 482 mApplyGravity = false; 483 } 484 if (needMirroring) { 485 canvas.save(); 486 // Mirror the bitmap 487 canvas.translate(mDstRect.right - mDstRect.left, 0); 488 canvas.scale(-1.0f, 1.0f); 489 } 490 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 491 if (needMirroring) { 492 canvas.restore(); 493 } 494 } else { 495 if (mApplyGravity) { 496 copyBounds(mDstRect); 497 mApplyGravity = false; 498 } 499 if (needMirroring) { 500 // Mirror the bitmap 501 updateMirrorMatrix(mDstRect.right - mDstRect.left); 502 shader.setLocalMatrix(mMirrorMatrix); 503 } else { 504 if (mMirrorMatrix != null) { 505 mMirrorMatrix = null; 506 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 507 } 508 } 509 canvas.drawRect(mDstRect, state.mPaint); 510 } 511 } 512 } 513 514 @Override 515 public void setAlpha(int alpha) { 516 int oldAlpha = mBitmapState.mPaint.getAlpha(); 517 if (alpha != oldAlpha) { 518 mBitmapState.mPaint.setAlpha(alpha); 519 invalidateSelf(); 520 } 521 } 522 523 @Override 524 public int getAlpha() { 525 return mBitmapState.mPaint.getAlpha(); 526 } 527 528 @Override 529 public void setColorFilter(ColorFilter cf) { 530 mBitmapState.mPaint.setColorFilter(cf); 531 invalidateSelf(); 532 } 533 534 /** 535 * @hide Candidate for future API inclusion 536 */ 537 public void setXfermode(Xfermode xfermode) { 538 mBitmapState.mPaint.setXfermode(xfermode); 539 invalidateSelf(); 540 } 541 542 /** 543 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 544 * that comes from the same resource. 545 * 546 * @return This drawable. 547 */ 548 @Override 549 public Drawable mutate() { 550 if (!mMutated && super.mutate() == this) { 551 mBitmapState = new BitmapState(mBitmapState); 552 mMutated = true; 553 } 554 return this; 555 } 556 557 @Override 558 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 559 throws XmlPullParserException, IOException { 560 super.inflate(r, parser, attrs); 561 562 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 563 564 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 565 if (id == 0) { 566 throw new XmlPullParserException(parser.getPositionDescription() + 567 ": <bitmap> requires a valid src attribute"); 568 } 569 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 570 if (bitmap == null) { 571 throw new XmlPullParserException(parser.getPositionDescription() + 572 ": <bitmap> requires a valid src attribute"); 573 } 574 mBitmapState.mBitmap = bitmap; 575 setBitmap(bitmap); 576 setTargetDensity(r.getDisplayMetrics()); 577 setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap, 578 bitmap.hasMipMap())); 579 setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored, 580 false)); 581 582 final Paint paint = mBitmapState.mPaint; 583 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 584 paint.isAntiAlias())); 585 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 586 paint.isFilterBitmap())); 587 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 588 paint.isDither())); 589 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 590 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 591 if (tileMode != -1) { 592 switch (tileMode) { 593 case 0: 594 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 595 break; 596 case 1: 597 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 598 break; 599 case 2: 600 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 601 break; 602 } 603 } 604 605 a.recycle(); 606 } 607 608 @Override 609 public int getIntrinsicWidth() { 610 return mBitmapWidth; 611 } 612 613 @Override 614 public int getIntrinsicHeight() { 615 return mBitmapHeight; 616 } 617 618 @Override 619 public int getOpacity() { 620 if (mBitmapState.mGravity != Gravity.FILL) { 621 return PixelFormat.TRANSLUCENT; 622 } 623 Bitmap bm = mBitmap; 624 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 625 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 626 } 627 628 @Override 629 public final ConstantState getConstantState() { 630 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 631 return mBitmapState; 632 } 633 634 final static class BitmapState extends ConstantState { 635 Bitmap mBitmap; 636 int mChangingConfigurations; 637 int mGravity = Gravity.FILL; 638 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 639 Shader.TileMode mTileModeX = null; 640 Shader.TileMode mTileModeY = null; 641 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 642 boolean mRebuildShader; 643 boolean mAutoMirrored; 644 645 BitmapState(Bitmap bitmap) { 646 mBitmap = bitmap; 647 } 648 649 BitmapState(BitmapState bitmapState) { 650 this(bitmapState.mBitmap); 651 mChangingConfigurations = bitmapState.mChangingConfigurations; 652 mGravity = bitmapState.mGravity; 653 mTileModeX = bitmapState.mTileModeX; 654 mTileModeY = bitmapState.mTileModeY; 655 mTargetDensity = bitmapState.mTargetDensity; 656 mPaint = new Paint(bitmapState.mPaint); 657 mRebuildShader = bitmapState.mRebuildShader; 658 mAutoMirrored = bitmapState.mAutoMirrored; 659 } 660 661 @Override 662 public Bitmap getBitmap() { 663 return mBitmap; 664 } 665 666 @Override 667 public Drawable newDrawable() { 668 return new BitmapDrawable(this, null); 669 } 670 671 @Override 672 public Drawable newDrawable(Resources res) { 673 return new BitmapDrawable(this, res); 674 } 675 676 @Override 677 public int getChangingConfigurations() { 678 return mChangingConfigurations; 679 } 680 } 681 682 private BitmapDrawable(BitmapState state, Resources res) { 683 mBitmapState = state; 684 if (res != null) { 685 mTargetDensity = res.getDisplayMetrics().densityDpi; 686 } else { 687 mTargetDensity = state.mTargetDensity; 688 } 689 setBitmap(state != null ? state.mBitmap : null); 690 } 691 } 692