BitmapDrawable.java revision 8e5e11b99fac942122ee2d6cdd30af51564861ae
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 final BitmapState state = mBitmapState; 622 state.mTint = tint; 623 state.mTintMode = tintMode; 624 625 mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); 626 invalidateSelf(); 627 } 628 629 /** 630 * @hide only needed by a hack within ProgressBar 631 */ 632 public ColorStateList getTint() { 633 return mBitmapState.mTint; 634 } 635 636 /** 637 * @hide only needed by a hack within ProgressBar 638 */ 639 public Mode getTintMode() { 640 return mBitmapState.mTintMode; 641 } 642 643 /** 644 * @hide Candidate for future API inclusion 645 */ 646 @Override 647 public void setXfermode(Xfermode xfermode) { 648 mBitmapState.mPaint.setXfermode(xfermode); 649 invalidateSelf(); 650 } 651 652 /** 653 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 654 * that comes from the same resource. 655 * 656 * @return This drawable. 657 */ 658 @Override 659 public Drawable mutate() { 660 if (!mMutated && super.mutate() == this) { 661 mBitmapState = new BitmapState(mBitmapState); 662 mMutated = true; 663 } 664 return this; 665 } 666 667 @Override 668 protected boolean onStateChange(int[] stateSet) { 669 final BitmapState state = mBitmapState; 670 if (state.mTint != null && state.mTintMode != null) { 671 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 672 return true; 673 } 674 return false; 675 } 676 677 @Override 678 public boolean isStateful() { 679 final BitmapState s = mBitmapState; 680 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 681 } 682 683 @Override 684 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 685 throws XmlPullParserException, IOException { 686 super.inflate(r, parser, attrs, theme); 687 688 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); 689 updateStateFromTypedArray(a); 690 verifyState(a); 691 a.recycle(); 692 } 693 694 /** 695 * Ensures all required attributes are set. 696 * 697 * @throws XmlPullParserException if any required attributes are missing 698 */ 699 private void verifyState(TypedArray a) throws XmlPullParserException { 700 final BitmapState state = mBitmapState; 701 if (state.mBitmap == null) { 702 throw new XmlPullParserException(a.getPositionDescription() + 703 ": <bitmap> requires a valid src attribute"); 704 } 705 } 706 707 /** 708 * Updates the constant state from the values in the typed array. 709 */ 710 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 711 final Resources r = a.getResources(); 712 final BitmapState state = mBitmapState; 713 714 // Account for any configuration changes. 715 state.mChangingConfigurations |= a.getChangingConfigurations(); 716 717 // Extract the theme attributes, if any. 718 state.mThemeAttrs = a.extractThemeAttrs(); 719 720 final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); 721 if (srcResId != 0) { 722 final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId); 723 if (bitmap == null) { 724 throw new XmlPullParserException(a.getPositionDescription() + 725 ": <bitmap> requires a valid src attribute"); 726 } 727 728 state.mBitmap = bitmap; 729 } 730 731 state.mTargetDensity = r.getDisplayMetrics().densityDpi; 732 733 final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; 734 setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); 735 736 state.mAutoMirrored = a.getBoolean( 737 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); 738 state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); 739 740 final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); 741 if (tintMode != -1) { 742 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 743 } 744 745 final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); 746 if (tint != null) { 747 state.mTint = tint; 748 } 749 750 final Paint paint = mBitmapState.mPaint; 751 paint.setAntiAlias(a.getBoolean( 752 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); 753 paint.setFilterBitmap(a.getBoolean( 754 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); 755 paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); 756 757 setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); 758 759 final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); 760 if (tileMode != TILE_MODE_UNDEFINED) { 761 final Shader.TileMode mode = parseTileMode(tileMode); 762 setTileModeXY(mode, mode); 763 } 764 765 final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); 766 if (tileModeX != TILE_MODE_UNDEFINED) { 767 setTileModeX(parseTileMode(tileModeX)); 768 } 769 770 final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); 771 if (tileModeY != TILE_MODE_UNDEFINED) { 772 setTileModeY(parseTileMode(tileModeY)); 773 } 774 775 // Update local properties. 776 initializeWithState(state, r); 777 } 778 779 @Override 780 public void applyTheme(Theme t) { 781 super.applyTheme(t); 782 783 final BitmapState state = mBitmapState; 784 if (state == null || state.mThemeAttrs == null) { 785 return; 786 } 787 788 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable); 789 try { 790 updateStateFromTypedArray(a); 791 } catch (XmlPullParserException e) { 792 throw new RuntimeException(e); 793 } finally { 794 a.recycle(); 795 } 796 } 797 798 private static Shader.TileMode parseTileMode(int tileMode) { 799 switch (tileMode) { 800 case TILE_MODE_CLAMP: 801 return Shader.TileMode.CLAMP; 802 case TILE_MODE_REPEAT: 803 return Shader.TileMode.REPEAT; 804 case TILE_MODE_MIRROR: 805 return Shader.TileMode.MIRROR; 806 default: 807 return null; 808 } 809 } 810 811 @Override 812 public boolean canApplyTheme() { 813 return mBitmapState != null && mBitmapState.mThemeAttrs != null; 814 } 815 816 @Override 817 public int getIntrinsicWidth() { 818 return mBitmapWidth; 819 } 820 821 @Override 822 public int getIntrinsicHeight() { 823 return mBitmapHeight; 824 } 825 826 @Override 827 public int getOpacity() { 828 if (mBitmapState.mGravity != Gravity.FILL) { 829 return PixelFormat.TRANSLUCENT; 830 } 831 832 final Bitmap bitmap = mBitmapState.mBitmap; 833 return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 834 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 835 } 836 837 @Override 838 public final ConstantState getConstantState() { 839 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 840 return mBitmapState; 841 } 842 843 final static class BitmapState extends ConstantState { 844 final Paint mPaint; 845 846 // Values loaded during inflation. 847 int[] mThemeAttrs = null; 848 Bitmap mBitmap = null; 849 ColorStateList mTint = null; 850 Mode mTintMode = Mode.SRC_IN; 851 int mGravity = Gravity.FILL; 852 float mBaseAlpha = 1.0f; 853 Shader.TileMode mTileModeX = null; 854 Shader.TileMode mTileModeY = null; 855 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 856 boolean mAutoMirrored = false; 857 858 int mChangingConfigurations; 859 boolean mRebuildShader; 860 861 BitmapState(Bitmap bitmap) { 862 mBitmap = bitmap; 863 mPaint = new Paint(DEFAULT_PAINT_FLAGS); 864 } 865 866 BitmapState(BitmapState bitmapState) { 867 mBitmap = bitmapState.mBitmap; 868 mTint = bitmapState.mTint; 869 mTintMode = bitmapState.mTintMode; 870 mThemeAttrs = bitmapState.mThemeAttrs; 871 mChangingConfigurations = bitmapState.mChangingConfigurations; 872 mGravity = bitmapState.mGravity; 873 mTileModeX = bitmapState.mTileModeX; 874 mTileModeY = bitmapState.mTileModeY; 875 mTargetDensity = bitmapState.mTargetDensity; 876 mBaseAlpha = bitmapState.mBaseAlpha; 877 mPaint = new Paint(bitmapState.mPaint); 878 mRebuildShader = bitmapState.mRebuildShader; 879 mAutoMirrored = bitmapState.mAutoMirrored; 880 } 881 882 @Override 883 public boolean canApplyTheme() { 884 return mThemeAttrs != null; 885 } 886 887 @Override 888 public Bitmap getBitmap() { 889 return mBitmap; 890 } 891 892 @Override 893 public Drawable newDrawable() { 894 return new BitmapDrawable(this, null, null); 895 } 896 897 @Override 898 public Drawable newDrawable(Resources res) { 899 return new BitmapDrawable(this, res, null); 900 } 901 902 @Override 903 public Drawable newDrawable(Resources res, Theme theme) { 904 return new BitmapDrawable(this, res, theme); 905 } 906 907 @Override 908 public int getChangingConfigurations() { 909 return mChangingConfigurations; 910 } 911 } 912 913 /** 914 * The one constructor to rule them all. This is called by all public 915 * constructors to set the state and initialize local properties. 916 */ 917 private BitmapDrawable(BitmapState state, Resources res, Theme theme) { 918 if (theme != null && state.canApplyTheme()) { 919 // If we need to apply a theme, implicitly mutate. 920 mBitmapState = new BitmapState(state); 921 applyTheme(theme); 922 } else { 923 mBitmapState = state; 924 } 925 926 initializeWithState(state, res); 927 } 928 929 /** 930 * Initializes local dynamic properties from state. This should be called 931 * after significant state changes, e.g. from the One True Constructor and 932 * after inflating or applying a theme. 933 */ 934 private void initializeWithState(BitmapState state, Resources res) { 935 if (res != null) { 936 mTargetDensity = res.getDisplayMetrics().densityDpi; 937 } else { 938 mTargetDensity = state.mTargetDensity; 939 } 940 941 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 942 computeBitmapSize(); 943 } 944} 945