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