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.annotation.NonNull; 20 import android.content.res.ColorStateList; 21 import android.content.res.Resources; 22 import android.content.res.Resources.Theme; 23 import android.content.res.TypedArray; 24 import android.graphics.Bitmap; 25 import android.graphics.BitmapFactory; 26 import android.graphics.BitmapShader; 27 import android.graphics.Canvas; 28 import android.graphics.ColorFilter; 29 import android.graphics.Insets; 30 import android.graphics.Matrix; 31 import android.graphics.Outline; 32 import android.graphics.Paint; 33 import android.graphics.PixelFormat; 34 import android.graphics.PorterDuff; 35 import android.graphics.PorterDuff.Mode; 36 import android.graphics.PorterDuffColorFilter; 37 import android.graphics.Rect; 38 import android.graphics.Shader; 39 import android.graphics.Xfermode; 40 import android.util.AttributeSet; 41 import android.util.DisplayMetrics; 42 import android.util.LayoutDirection; 43 import android.view.Gravity; 44 45 import com.android.internal.R; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import java.io.IOException; 51 52 /** 53 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 54 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 55 * a {@link android.graphics.Bitmap} object. 56 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 57 * information, see the guide to <a 58 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 59 * <p> 60 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 61 * transformation of raw bitmap graphics, and should be used when drawing to a 62 * {@link android.graphics.Canvas}. 63 * </p> 64 * 65 * @attr ref android.R.styleable#BitmapDrawable_src 66 * @attr ref android.R.styleable#BitmapDrawable_antialias 67 * @attr ref android.R.styleable#BitmapDrawable_filter 68 * @attr ref android.R.styleable#BitmapDrawable_dither 69 * @attr ref android.R.styleable#BitmapDrawable_gravity 70 * @attr ref android.R.styleable#BitmapDrawable_mipMap 71 * @attr ref android.R.styleable#BitmapDrawable_tileMode 72 */ 73 public class BitmapDrawable extends Drawable { 74 private static final int DEFAULT_PAINT_FLAGS = 75 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 76 77 // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}. 78 private static final int TILE_MODE_UNDEFINED = -2; 79 private static final int TILE_MODE_DISABLED = -1; 80 private static final int TILE_MODE_CLAMP = 0; 81 private static final int TILE_MODE_REPEAT = 1; 82 private static final int TILE_MODE_MIRROR = 2; 83 84 private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this 85 86 private BitmapState mBitmapState; 87 private PorterDuffColorFilter mTintFilter; 88 89 private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 90 91 private boolean mDstRectAndInsetsDirty = true; 92 private boolean mMutated; 93 94 // These are scaled to match the target density. 95 private int mBitmapWidth; 96 private int mBitmapHeight; 97 98 /** Optical insets due to gravity. */ 99 private Insets mOpticalInsets = Insets.NONE; 100 101 // Mirroring matrix for using with Shaders 102 private Matrix mMirrorMatrix; 103 104 /** 105 * Create an empty drawable, not dealing with density. 106 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 107 * instead to specify a bitmap to draw with and ensure the correct density is set. 108 */ 109 @Deprecated 110 public BitmapDrawable() { 111 mBitmapState = new BitmapState((Bitmap) null); 112 } 113 114 /** 115 * Create an empty drawable, setting initial target density based on 116 * the display metrics of the resources. 117 * 118 * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} 119 * instead to specify a bitmap to draw with. 120 */ 121 @SuppressWarnings("unused") 122 @Deprecated 123 public BitmapDrawable(Resources res) { 124 mBitmapState = new BitmapState((Bitmap) null); 125 mBitmapState.mTargetDensity = mTargetDensity; 126 } 127 128 /** 129 * Create drawable from a bitmap, not dealing with density. 130 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 131 * that the drawable has correctly set its target density. 132 */ 133 @Deprecated 134 public BitmapDrawable(Bitmap bitmap) { 135 this(new BitmapState(bitmap), null, null); 136 } 137 138 /** 139 * Create drawable from a bitmap, setting initial target density based on 140 * the display metrics of the resources. 141 */ 142 public BitmapDrawable(Resources res, Bitmap bitmap) { 143 this(new BitmapState(bitmap), res, null); 144 mBitmapState.mTargetDensity = mTargetDensity; 145 } 146 147 /** 148 * Create a drawable by opening a given file path and decoding the bitmap. 149 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 150 * that the drawable has correctly set its target density. 151 */ 152 @Deprecated 153 public BitmapDrawable(String filepath) { 154 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); 155 if (mBitmapState.mBitmap == null) { 156 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 157 } 158 } 159 160 /** 161 * Create a drawable by opening a given file path and decoding the bitmap. 162 */ 163 @SuppressWarnings("unused") 164 public BitmapDrawable(Resources res, String filepath) { 165 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); 166 mBitmapState.mTargetDensity = mTargetDensity; 167 if (mBitmapState.mBitmap == null) { 168 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 169 } 170 } 171 172 /** 173 * Create a drawable by decoding a bitmap from the given input stream. 174 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 175 * that the drawable has correctly set its target density. 176 */ 177 @Deprecated 178 public BitmapDrawable(java.io.InputStream is) { 179 this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); 180 if (mBitmapState.mBitmap == null) { 181 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 182 } 183 } 184 185 /** 186 * Create a drawable by decoding a bitmap from the given input stream. 187 */ 188 @SuppressWarnings("unused") 189 public BitmapDrawable(Resources res, java.io.InputStream is) { 190 this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); 191 mBitmapState.mTargetDensity = mTargetDensity; 192 if (mBitmapState.mBitmap == null) { 193 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 194 } 195 } 196 197 /** 198 * Returns the paint used to render this drawable. 199 */ 200 public final Paint getPaint() { 201 return mBitmapState.mPaint; 202 } 203 204 /** 205 * Returns the bitmap used by this drawable to render. May be null. 206 */ 207 public final Bitmap getBitmap() { 208 return mBitmapState.mBitmap; 209 } 210 211 private void computeBitmapSize() { 212 final Bitmap bitmap = mBitmapState.mBitmap; 213 if (bitmap != null) { 214 mBitmapWidth = bitmap.getScaledWidth(mTargetDensity); 215 mBitmapHeight = bitmap.getScaledHeight(mTargetDensity); 216 } else { 217 mBitmapWidth = mBitmapHeight = -1; 218 } 219 } 220 221 private void setBitmap(Bitmap bitmap) { 222 if (mBitmapState.mBitmap != bitmap) { 223 mBitmapState.mBitmap = bitmap; 224 computeBitmapSize(); 225 invalidateSelf(); 226 } 227 } 228 229 /** 230 * Set the density scale at which this drawable will be rendered. This 231 * method assumes the drawable will be rendered at the same density as the 232 * specified canvas. 233 * 234 * @param canvas The Canvas from which the density scale must be obtained. 235 * 236 * @see android.graphics.Bitmap#setDensity(int) 237 * @see android.graphics.Bitmap#getDensity() 238 */ 239 public void setTargetDensity(Canvas canvas) { 240 setTargetDensity(canvas.getDensity()); 241 } 242 243 /** 244 * Set the density scale at which this drawable will be rendered. 245 * 246 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 247 * 248 * @see android.graphics.Bitmap#setDensity(int) 249 * @see android.graphics.Bitmap#getDensity() 250 */ 251 public void setTargetDensity(DisplayMetrics metrics) { 252 setTargetDensity(metrics.densityDpi); 253 } 254 255 /** 256 * Set the density at which this drawable will be rendered. 257 * 258 * @param density The density scale for this drawable. 259 * 260 * @see android.graphics.Bitmap#setDensity(int) 261 * @see android.graphics.Bitmap#getDensity() 262 */ 263 public void setTargetDensity(int density) { 264 if (mTargetDensity != density) { 265 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 266 if (mBitmapState.mBitmap != null) { 267 computeBitmapSize(); 268 } 269 invalidateSelf(); 270 } 271 } 272 273 /** Get the gravity used to position/stretch the bitmap within its bounds. 274 * See android.view.Gravity 275 * @return the gravity applied to the bitmap 276 */ 277 public int getGravity() { 278 return mBitmapState.mGravity; 279 } 280 281 /** Set the gravity used to position/stretch the bitmap within its bounds. 282 See android.view.Gravity 283 * @param gravity the gravity 284 */ 285 public void setGravity(int gravity) { 286 if (mBitmapState.mGravity != gravity) { 287 mBitmapState.mGravity = gravity; 288 mDstRectAndInsetsDirty = true; 289 invalidateSelf(); 290 } 291 } 292 293 /** 294 * Enables or disables the mipmap hint for this drawable's bitmap. 295 * See {@link Bitmap#setHasMipMap(boolean)} for more information. 296 * 297 * If the bitmap is null calling this method has no effect. 298 * 299 * @param mipMap True if the bitmap should use mipmaps, false otherwise. 300 * 301 * @see #hasMipMap() 302 */ 303 public void setMipMap(boolean mipMap) { 304 if (mBitmapState.mBitmap != null) { 305 mBitmapState.mBitmap.setHasMipMap(mipMap); 306 invalidateSelf(); 307 } 308 } 309 310 /** 311 * Indicates whether the mipmap hint is enabled on this drawable's bitmap. 312 * 313 * @return True if the mipmap hint is set, false otherwise. If the bitmap 314 * is null, this method always returns false. 315 * 316 * @see #setMipMap(boolean) 317 * @attr ref android.R.styleable#BitmapDrawable_mipMap 318 */ 319 public boolean hasMipMap() { 320 return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap(); 321 } 322 323 /** 324 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 325 * the edges of the bitmap only so it applies only when the drawable is rotated. 326 * 327 * @param aa True if the bitmap should be anti-aliased, false otherwise. 328 * 329 * @see #hasAntiAlias() 330 */ 331 public void setAntiAlias(boolean aa) { 332 mBitmapState.mPaint.setAntiAlias(aa); 333 invalidateSelf(); 334 } 335 336 /** 337 * Indicates whether anti-aliasing is enabled for this drawable. 338 * 339 * @return True if anti-aliasing is enabled, false otherwise. 340 * 341 * @see #setAntiAlias(boolean) 342 */ 343 public boolean hasAntiAlias() { 344 return mBitmapState.mPaint.isAntiAlias(); 345 } 346 347 @Override 348 public void setFilterBitmap(boolean filter) { 349 mBitmapState.mPaint.setFilterBitmap(filter); 350 invalidateSelf(); 351 } 352 353 @Override 354 public void setDither(boolean dither) { 355 mBitmapState.mPaint.setDither(dither); 356 invalidateSelf(); 357 } 358 359 /** 360 * Indicates the repeat behavior of this drawable on the X axis. 361 * 362 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 363 * {@link android.graphics.Shader.TileMode#REPEAT} or 364 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 365 */ 366 public Shader.TileMode getTileModeX() { 367 return mBitmapState.mTileModeX; 368 } 369 370 /** 371 * Indicates the repeat behavior of this drawable on the Y axis. 372 * 373 * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, 374 * {@link android.graphics.Shader.TileMode#REPEAT} or 375 * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. 376 */ 377 public Shader.TileMode getTileModeY() { 378 return mBitmapState.mTileModeY; 379 } 380 381 /** 382 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 383 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 384 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 385 * if the bitmap is smaller than this drawable. 386 * 387 * @param mode The repeat mode for this drawable. 388 * 389 * @see #setTileModeY(android.graphics.Shader.TileMode) 390 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 391 * @attr ref android.R.styleable#BitmapDrawable_tileModeX 392 */ 393 public void setTileModeX(Shader.TileMode mode) { 394 setTileModeXY(mode, mBitmapState.mTileModeY); 395 } 396 397 /** 398 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 399 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 400 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 401 * if the bitmap is smaller than this drawable. 402 * 403 * @param mode The repeat mode for this drawable. 404 * 405 * @see #setTileModeX(android.graphics.Shader.TileMode) 406 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 407 * @attr ref android.R.styleable#BitmapDrawable_tileModeY 408 */ 409 public final void setTileModeY(Shader.TileMode mode) { 410 setTileModeXY(mBitmapState.mTileModeX, mode); 411 } 412 413 /** 414 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 415 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 416 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 417 * if the bitmap is smaller than this drawable. 418 * 419 * @param xmode The X repeat mode for this drawable. 420 * @param ymode The Y repeat mode for this drawable. 421 * 422 * @see #setTileModeX(android.graphics.Shader.TileMode) 423 * @see #setTileModeY(android.graphics.Shader.TileMode) 424 */ 425 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 426 final BitmapState state = mBitmapState; 427 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 428 state.mTileModeX = xmode; 429 state.mTileModeY = ymode; 430 state.mRebuildShader = true; 431 mDstRectAndInsetsDirty = true; 432 invalidateSelf(); 433 } 434 } 435 436 @Override 437 public void setAutoMirrored(boolean mirrored) { 438 if (mBitmapState.mAutoMirrored != mirrored) { 439 mBitmapState.mAutoMirrored = mirrored; 440 invalidateSelf(); 441 } 442 } 443 444 @Override 445 public final boolean isAutoMirrored() { 446 return mBitmapState.mAutoMirrored; 447 } 448 449 @Override 450 public int getChangingConfigurations() { 451 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 452 } 453 454 private boolean needMirroring() { 455 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 456 } 457 458 private void updateMirrorMatrix(float dx) { 459 if (mMirrorMatrix == null) { 460 mMirrorMatrix = new Matrix(); 461 } 462 mMirrorMatrix.setTranslate(dx, 0); 463 mMirrorMatrix.preScale(-1.0f, 1.0f); 464 } 465 466 @Override 467 protected void onBoundsChange(Rect bounds) { 468 mDstRectAndInsetsDirty = true; 469 470 final Shader shader = mBitmapState.mPaint.getShader(); 471 if (shader != null) { 472 if (needMirroring()) { 473 updateMirrorMatrix(bounds.right - bounds.left); 474 shader.setLocalMatrix(mMirrorMatrix); 475 mBitmapState.mPaint.setShader(shader); 476 } else { 477 if (mMirrorMatrix != null) { 478 mMirrorMatrix = null; 479 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 480 mBitmapState.mPaint.setShader(shader); 481 } 482 } 483 } 484 } 485 486 @Override 487 public void draw(Canvas canvas) { 488 final Bitmap bitmap = mBitmapState.mBitmap; 489 if (bitmap == null) { 490 return; 491 } 492 493 final BitmapState state = mBitmapState; 494 final Paint paint = state.mPaint; 495 if (state.mRebuildShader) { 496 final Shader.TileMode tmx = state.mTileModeX; 497 final Shader.TileMode tmy = state.mTileModeY; 498 if (tmx == null && tmy == null) { 499 paint.setShader(null); 500 } else { 501 paint.setShader(new BitmapShader(bitmap, 502 tmx == null ? Shader.TileMode.CLAMP : tmx, 503 tmy == null ? Shader.TileMode.CLAMP : tmy)); 504 } 505 506 state.mRebuildShader = false; 507 } 508 509 final int restoreAlpha; 510 if (state.mBaseAlpha != 1.0f) { 511 final Paint p = getPaint(); 512 restoreAlpha = p.getAlpha(); 513 p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f)); 514 } else { 515 restoreAlpha = -1; 516 } 517 518 final boolean clearColorFilter; 519 if (mTintFilter != null && paint.getColorFilter() == null) { 520 paint.setColorFilter(mTintFilter); 521 clearColorFilter = true; 522 } else { 523 clearColorFilter = false; 524 } 525 526 updateDstRectAndInsetsIfDirty(); 527 final Shader shader = paint.getShader(); 528 final boolean needMirroring = needMirroring(); 529 if (shader == null) { 530 if (needMirroring) { 531 canvas.save(); 532 // Mirror the bitmap 533 canvas.translate(mDstRect.right - mDstRect.left, 0); 534 canvas.scale(-1.0f, 1.0f); 535 } 536 537 canvas.drawBitmap(bitmap, null, mDstRect, paint); 538 539 if (needMirroring) { 540 canvas.restore(); 541 } 542 } else { 543 if (needMirroring) { 544 // Mirror the bitmap 545 updateMirrorMatrix(mDstRect.right - mDstRect.left); 546 shader.setLocalMatrix(mMirrorMatrix); 547 paint.setShader(shader); 548 } else { 549 if (mMirrorMatrix != null) { 550 mMirrorMatrix = null; 551 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 552 paint.setShader(shader); 553 } 554 } 555 556 canvas.drawRect(mDstRect, paint); 557 } 558 559 if (clearColorFilter) { 560 paint.setColorFilter(null); 561 } 562 563 if (restoreAlpha >= 0) { 564 paint.setAlpha(restoreAlpha); 565 } 566 } 567 568 private void updateDstRectAndInsetsIfDirty() { 569 if (mDstRectAndInsetsDirty) { 570 if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) { 571 final Rect bounds = getBounds(); 572 final int layoutDirection = getLayoutDirection(); 573 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight, 574 bounds, mDstRect, layoutDirection); 575 576 final int left = mDstRect.left - bounds.left; 577 final int top = mDstRect.top - bounds.top; 578 final int right = bounds.right - mDstRect.right; 579 final int bottom = bounds.bottom - mDstRect.bottom; 580 mOpticalInsets = Insets.of(left, top, right, bottom); 581 } else { 582 copyBounds(mDstRect); 583 mOpticalInsets = Insets.NONE; 584 } 585 } 586 mDstRectAndInsetsDirty = false; 587 } 588 589 /** 590 * @hide 591 */ 592 @Override 593 public Insets getOpticalInsets() { 594 updateDstRectAndInsetsIfDirty(); 595 return mOpticalInsets; 596 } 597 598 @Override 599 public void getOutline(@NonNull Outline outline) { 600 updateDstRectAndInsetsIfDirty(); 601 outline.setRect(mDstRect); 602 603 // Only opaque Bitmaps can report a non-0 alpha, 604 // since only they are guaranteed to fill their bounds 605 boolean opaqueOverShape = mBitmapState.mBitmap != null 606 && !mBitmapState.mBitmap.hasAlpha(); 607 outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f); 608 } 609 610 @Override 611 public void setAlpha(int alpha) { 612 final int oldAlpha = mBitmapState.mPaint.getAlpha(); 613 if (alpha != oldAlpha) { 614 mBitmapState.mPaint.setAlpha(alpha); 615 invalidateSelf(); 616 } 617 } 618 619 @Override 620 public int getAlpha() { 621 return mBitmapState.mPaint.getAlpha(); 622 } 623 624 @Override 625 public void setColorFilter(ColorFilter cf) { 626 mBitmapState.mPaint.setColorFilter(cf); 627 invalidateSelf(); 628 } 629 630 @Override 631 public ColorFilter getColorFilter() { 632 return mBitmapState.mPaint.getColorFilter(); 633 } 634 635 @Override 636 public void setTintList(ColorStateList tint) { 637 mBitmapState.mTint = tint; 638 mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode); 639 invalidateSelf(); 640 } 641 642 @Override 643 public void setTintMode(PorterDuff.Mode tintMode) { 644 mBitmapState.mTintMode = tintMode; 645 mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode); 646 invalidateSelf(); 647 } 648 649 /** 650 * @hide only needed by a hack within ProgressBar 651 */ 652 public ColorStateList getTint() { 653 return mBitmapState.mTint; 654 } 655 656 /** 657 * @hide only needed by a hack within ProgressBar 658 */ 659 public Mode getTintMode() { 660 return mBitmapState.mTintMode; 661 } 662 663 /** 664 * @hide Candidate for future API inclusion 665 */ 666 @Override 667 public void setXfermode(Xfermode xfermode) { 668 mBitmapState.mPaint.setXfermode(xfermode); 669 invalidateSelf(); 670 } 671 672 /** 673 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 674 * that comes from the same resource. 675 * 676 * @return This drawable. 677 */ 678 @Override 679 public Drawable mutate() { 680 if (!mMutated && super.mutate() == this) { 681 mBitmapState = new BitmapState(mBitmapState); 682 mMutated = true; 683 } 684 return this; 685 } 686 687 @Override 688 protected boolean onStateChange(int[] stateSet) { 689 final BitmapState state = mBitmapState; 690 if (state.mTint != null && state.mTintMode != null) { 691 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 692 return true; 693 } 694 return false; 695 } 696 697 @Override 698 public boolean isStateful() { 699 final BitmapState s = mBitmapState; 700 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 701 } 702 703 @Override 704 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 705 throws XmlPullParserException, IOException { 706 super.inflate(r, parser, attrs, theme); 707 708 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); 709 updateStateFromTypedArray(a); 710 verifyState(a); 711 a.recycle(); 712 } 713 714 /** 715 * Ensures all required attributes are set. 716 * 717 * @throws XmlPullParserException if any required attributes are missing 718 */ 719 private void verifyState(TypedArray a) throws XmlPullParserException { 720 final BitmapState state = mBitmapState; 721 if (state.mBitmap == null) { 722 throw new XmlPullParserException(a.getPositionDescription() + 723 ": <bitmap> requires a valid src attribute"); 724 } 725 } 726 727 /** 728 * Updates the constant state from the values in the typed array. 729 */ 730 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 731 final Resources r = a.getResources(); 732 final BitmapState state = mBitmapState; 733 734 // Account for any configuration changes. 735 state.mChangingConfigurations |= a.getChangingConfigurations(); 736 737 // Extract the theme attributes, if any. 738 state.mThemeAttrs = a.extractThemeAttrs(); 739 740 final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); 741 if (srcResId != 0) { 742 final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId); 743 if (bitmap == null) { 744 throw new XmlPullParserException(a.getPositionDescription() + 745 ": <bitmap> requires a valid src attribute"); 746 } 747 748 state.mBitmap = bitmap; 749 } 750 751 state.mTargetDensity = r.getDisplayMetrics().densityDpi; 752 753 final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; 754 setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); 755 756 state.mAutoMirrored = a.getBoolean( 757 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); 758 state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); 759 760 final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); 761 if (tintMode != -1) { 762 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 763 } 764 765 final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); 766 if (tint != null) { 767 state.mTint = tint; 768 } 769 770 final Paint paint = mBitmapState.mPaint; 771 paint.setAntiAlias(a.getBoolean( 772 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); 773 paint.setFilterBitmap(a.getBoolean( 774 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); 775 paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); 776 777 setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); 778 779 final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); 780 if (tileMode != TILE_MODE_UNDEFINED) { 781 final Shader.TileMode mode = parseTileMode(tileMode); 782 setTileModeXY(mode, mode); 783 } 784 785 final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); 786 if (tileModeX != TILE_MODE_UNDEFINED) { 787 setTileModeX(parseTileMode(tileModeX)); 788 } 789 790 final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); 791 if (tileModeY != TILE_MODE_UNDEFINED) { 792 setTileModeY(parseTileMode(tileModeY)); 793 } 794 795 // Update local properties. 796 initializeWithState(state, r); 797 } 798 799 @Override 800 public void applyTheme(Theme t) { 801 super.applyTheme(t); 802 803 final BitmapState state = mBitmapState; 804 if (state == null || state.mThemeAttrs == null) { 805 return; 806 } 807 808 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable); 809 try { 810 updateStateFromTypedArray(a); 811 } catch (XmlPullParserException e) { 812 throw new RuntimeException(e); 813 } finally { 814 a.recycle(); 815 } 816 } 817 818 private static Shader.TileMode parseTileMode(int tileMode) { 819 switch (tileMode) { 820 case TILE_MODE_CLAMP: 821 return Shader.TileMode.CLAMP; 822 case TILE_MODE_REPEAT: 823 return Shader.TileMode.REPEAT; 824 case TILE_MODE_MIRROR: 825 return Shader.TileMode.MIRROR; 826 default: 827 return null; 828 } 829 } 830 831 @Override 832 public boolean canApplyTheme() { 833 return mBitmapState != null && mBitmapState.mThemeAttrs != null; 834 } 835 836 @Override 837 public int getIntrinsicWidth() { 838 return mBitmapWidth; 839 } 840 841 @Override 842 public int getIntrinsicHeight() { 843 return mBitmapHeight; 844 } 845 846 @Override 847 public int getOpacity() { 848 if (mBitmapState.mGravity != Gravity.FILL) { 849 return PixelFormat.TRANSLUCENT; 850 } 851 852 final Bitmap bitmap = mBitmapState.mBitmap; 853 return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 854 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 855 } 856 857 @Override 858 public final ConstantState getConstantState() { 859 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 860 return mBitmapState; 861 } 862 863 final static class BitmapState extends ConstantState { 864 final Paint mPaint; 865 866 // Values loaded during inflation. 867 int[] mThemeAttrs = null; 868 Bitmap mBitmap = null; 869 ColorStateList mTint = null; 870 Mode mTintMode = DEFAULT_TINT_MODE; 871 int mGravity = Gravity.FILL; 872 float mBaseAlpha = 1.0f; 873 Shader.TileMode mTileModeX = null; 874 Shader.TileMode mTileModeY = null; 875 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 876 boolean mAutoMirrored = false; 877 878 int mChangingConfigurations; 879 boolean mRebuildShader; 880 881 BitmapState(Bitmap bitmap) { 882 mBitmap = bitmap; 883 mPaint = new Paint(DEFAULT_PAINT_FLAGS); 884 } 885 886 BitmapState(BitmapState bitmapState) { 887 mBitmap = bitmapState.mBitmap; 888 mTint = bitmapState.mTint; 889 mTintMode = bitmapState.mTintMode; 890 mThemeAttrs = bitmapState.mThemeAttrs; 891 mChangingConfigurations = bitmapState.mChangingConfigurations; 892 mGravity = bitmapState.mGravity; 893 mTileModeX = bitmapState.mTileModeX; 894 mTileModeY = bitmapState.mTileModeY; 895 mTargetDensity = bitmapState.mTargetDensity; 896 mBaseAlpha = bitmapState.mBaseAlpha; 897 mPaint = new Paint(bitmapState.mPaint); 898 mRebuildShader = bitmapState.mRebuildShader; 899 mAutoMirrored = bitmapState.mAutoMirrored; 900 } 901 902 @Override 903 public boolean canApplyTheme() { 904 return mThemeAttrs != null; 905 } 906 907 @Override 908 public Bitmap getBitmap() { 909 return mBitmap; 910 } 911 912 @Override 913 public Drawable newDrawable() { 914 return new BitmapDrawable(this, null, null); 915 } 916 917 @Override 918 public Drawable newDrawable(Resources res) { 919 return new BitmapDrawable(this, res, null); 920 } 921 922 @Override 923 public Drawable newDrawable(Resources res, Theme theme) { 924 return new BitmapDrawable(this, res, theme); 925 } 926 927 @Override 928 public int getChangingConfigurations() { 929 return mChangingConfigurations; 930 } 931 } 932 933 /** 934 * The one constructor to rule them all. This is called by all public 935 * constructors to set the state and initialize local properties. 936 */ 937 private BitmapDrawable(BitmapState state, Resources res, Theme theme) { 938 if (theme != null && state.canApplyTheme()) { 939 // If we need to apply a theme, implicitly mutate. 940 mBitmapState = new BitmapState(state); 941 applyTheme(theme); 942 } else { 943 mBitmapState = state; 944 } 945 946 initializeWithState(state, res); 947 } 948 949 /** 950 * Initializes local dynamic properties from state. This should be called 951 * after significant state changes, e.g. from the One True Constructor and 952 * after inflating or applying a theme. 953 */ 954 private void initializeWithState(BitmapState state, Resources res) { 955 if (res != null) { 956 mTargetDensity = res.getDisplayMetrics().densityDpi; 957 } else { 958 mTargetDensity = state.mTargetDensity; 959 } 960 961 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 962 computeBitmapSize(); 963 } 964 } 965