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