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