BitmapDrawable.java revision 4f64c048505a432e549ccb756634ecebf28f9e80
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 * @attr ref android.R.styleable#BitmapDrawable_tileModeX 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 * @attr ref android.R.styleable#BitmapDrawable_tileModeY 406 */ 407 public final void setTileModeY(Shader.TileMode mode) { 408 setTileModeXY(mBitmapState.mTileModeX, mode); 409 } 410 411 /** 412 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 413 * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or 414 * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) 415 * if the bitmap is smaller than this drawable. 416 * 417 * @param xmode The X repeat mode for this drawable. 418 * @param ymode The Y repeat mode for this drawable. 419 * 420 * @see #setTileModeX(android.graphics.Shader.TileMode) 421 * @see #setTileModeY(android.graphics.Shader.TileMode) 422 */ 423 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 424 final BitmapState state = mBitmapState; 425 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 426 state.mTileModeX = xmode; 427 state.mTileModeY = ymode; 428 state.mRebuildShader = true; 429 invalidateSelf(); 430 } 431 } 432 433 @Override 434 public void setAutoMirrored(boolean mirrored) { 435 if (mBitmapState.mAutoMirrored != mirrored) { 436 mBitmapState.mAutoMirrored = mirrored; 437 invalidateSelf(); 438 } 439 } 440 441 @Override 442 public final boolean isAutoMirrored() { 443 return mBitmapState.mAutoMirrored; 444 } 445 446 @Override 447 public int getChangingConfigurations() { 448 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 449 } 450 451 private boolean needMirroring() { 452 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 453 } 454 455 private void updateMirrorMatrix(float dx) { 456 if (mMirrorMatrix == null) { 457 mMirrorMatrix = new Matrix(); 458 } 459 mMirrorMatrix.setTranslate(dx, 0); 460 mMirrorMatrix.preScale(-1.0f, 1.0f); 461 } 462 463 @Override 464 protected void onBoundsChange(Rect bounds) { 465 mApplyGravity = true; 466 467 final Shader shader = mBitmapState.mPaint.getShader(); 468 if (shader != null) { 469 if (needMirroring()) { 470 updateMirrorMatrix(bounds.right - bounds.left); 471 shader.setLocalMatrix(mMirrorMatrix); 472 mBitmapState.mPaint.setShader(shader); 473 } else { 474 if (mMirrorMatrix != null) { 475 mMirrorMatrix = null; 476 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 477 mBitmapState.mPaint.setShader(shader); 478 } 479 } 480 } 481 } 482 483 @Override 484 public void draw(Canvas canvas) { 485 final Bitmap bitmap = mBitmapState.mBitmap; 486 if (bitmap == null) { 487 return; 488 } 489 490 final BitmapState state = mBitmapState; 491 final Paint paint = state.mPaint; 492 if (state.mRebuildShader) { 493 final Shader.TileMode tmx = state.mTileModeX; 494 final Shader.TileMode tmy = state.mTileModeY; 495 if (tmx == null && tmy == null) { 496 paint.setShader(null); 497 } else { 498 paint.setShader(new BitmapShader(bitmap, 499 tmx == null ? Shader.TileMode.CLAMP : tmx, 500 tmy == null ? Shader.TileMode.CLAMP : tmy)); 501 } 502 503 state.mRebuildShader = false; 504 copyBounds(mDstRect); 505 } 506 507 final int restoreAlpha; 508 if (state.mBaseAlpha != 1.0f) { 509 final Paint p = getPaint(); 510 restoreAlpha = p.getAlpha(); 511 p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f)); 512 } else { 513 restoreAlpha = -1; 514 } 515 516 final boolean clearColorFilter; 517 if (mTintFilter != null && paint.getColorFilter() == null) { 518 paint.setColorFilter(mTintFilter); 519 clearColorFilter = true; 520 } else { 521 clearColorFilter = false; 522 } 523 524 final Shader shader = paint.getShader(); 525 final boolean needMirroring = needMirroring(); 526 if (shader == null) { 527 if (mApplyGravity) { 528 applyGravity(); 529 mApplyGravity = false; 530 } 531 532 if (needMirroring) { 533 canvas.save(); 534 // Mirror the bitmap 535 canvas.translate(mDstRect.right - mDstRect.left, 0); 536 canvas.scale(-1.0f, 1.0f); 537 } 538 539 canvas.drawBitmap(bitmap, null, mDstRect, paint); 540 541 if (needMirroring) { 542 canvas.restore(); 543 } 544 } else { 545 if (mApplyGravity) { 546 copyBounds(mDstRect); 547 mApplyGravity = false; 548 } 549 550 if (needMirroring) { 551 // Mirror the bitmap 552 updateMirrorMatrix(mDstRect.right - mDstRect.left); 553 shader.setLocalMatrix(mMirrorMatrix); 554 paint.setShader(shader); 555 } else { 556 if (mMirrorMatrix != null) { 557 mMirrorMatrix = null; 558 shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); 559 paint.setShader(shader); 560 } 561 } 562 563 canvas.drawRect(mDstRect, paint); 564 } 565 566 if (clearColorFilter) { 567 paint.setColorFilter(null); 568 } 569 570 if (restoreAlpha >= 0) { 571 paint.setAlpha(restoreAlpha); 572 } 573 } 574 575 /** 576 * @hide 577 */ 578 @Override 579 public Insets getOpticalInsets() { 580 if (mApplyGravity && mBitmapState.mPaint.getShader() == null) { 581 applyGravity(); 582 mApplyGravity = false; 583 } 584 return mOpticalInsets == null ? Insets.NONE : mOpticalInsets; 585 } 586 587 private void applyGravity() { 588 final Rect bounds = getBounds(); 589 final int layoutDirection = getLayoutDirection(); 590 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight, 591 bounds, mDstRect, layoutDirection); 592 593 final int left = mDstRect.left - bounds.left; 594 final int top = mDstRect.top - bounds.top; 595 final int right = bounds.right - mDstRect.right; 596 final int bottom = bounds.bottom - mDstRect.bottom; 597 mOpticalInsets = Insets.of(left, top, right, bottom); 598 } 599 600 @Override 601 public void setAlpha(int alpha) { 602 final int oldAlpha = mBitmapState.mPaint.getAlpha(); 603 if (alpha != oldAlpha) { 604 mBitmapState.mPaint.setAlpha(alpha); 605 invalidateSelf(); 606 } 607 } 608 609 @Override 610 public int getAlpha() { 611 return mBitmapState.mPaint.getAlpha(); 612 } 613 614 @Override 615 public void setColorFilter(ColorFilter cf) { 616 mBitmapState.mPaint.setColorFilter(cf); 617 invalidateSelf(); 618 } 619 620 @Override 621 public ColorFilter getColorFilter() { 622 return mBitmapState.mPaint.getColorFilter(); 623 } 624 625 @Override 626 public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) { 627 final BitmapState state = mBitmapState; 628 state.mTint = tint; 629 state.mTintMode = tintMode; 630 631 mTintFilter = updateTintFilter(mTintFilter, tint, tintMode); 632 invalidateSelf(); 633 } 634 635 /** 636 * @hide only needed by a hack within ProgressBar 637 */ 638 public ColorStateList getTint() { 639 return mBitmapState.mTint; 640 } 641 642 /** 643 * @hide only needed by a hack within ProgressBar 644 */ 645 public Mode getTintMode() { 646 return mBitmapState.mTintMode; 647 } 648 649 /** 650 * @hide Candidate for future API inclusion 651 */ 652 @Override 653 public void setXfermode(Xfermode xfermode) { 654 mBitmapState.mPaint.setXfermode(xfermode); 655 invalidateSelf(); 656 } 657 658 /** 659 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 660 * that comes from the same resource. 661 * 662 * @return This drawable. 663 */ 664 @Override 665 public Drawable mutate() { 666 if (!mMutated && super.mutate() == this) { 667 mBitmapState = new BitmapState(mBitmapState); 668 mMutated = true; 669 } 670 return this; 671 } 672 673 @Override 674 protected boolean onStateChange(int[] stateSet) { 675 final BitmapState state = mBitmapState; 676 if (state.mTint != null && state.mTintMode != null) { 677 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 678 return true; 679 } 680 return false; 681 } 682 683 @Override 684 public boolean isStateful() { 685 final BitmapState s = mBitmapState; 686 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 687 } 688 689 @Override 690 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 691 throws XmlPullParserException, IOException { 692 super.inflate(r, parser, attrs, theme); 693 694 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); 695 updateStateFromTypedArray(a); 696 verifyState(a); 697 a.recycle(); 698 } 699 700 /** 701 * Ensures all required attributes are set. 702 * 703 * @throws XmlPullParserException if any required attributes are missing 704 */ 705 private void verifyState(TypedArray a) throws XmlPullParserException { 706 final BitmapState state = mBitmapState; 707 if (state.mBitmap == null) { 708 throw new XmlPullParserException(a.getPositionDescription() + 709 ": <bitmap> requires a valid src attribute"); 710 } 711 } 712 713 /** 714 * Updates the constant state from the values in the typed array. 715 */ 716 private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 717 final Resources r = a.getResources(); 718 final BitmapState state = mBitmapState; 719 720 // Account for any configuration changes. 721 state.mChangingConfigurations |= a.getChangingConfigurations(); 722 723 // Extract the theme attributes, if any. 724 state.mThemeAttrs = a.extractThemeAttrs(); 725 726 final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); 727 if (srcResId != 0) { 728 final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId); 729 if (bitmap == null) { 730 throw new XmlPullParserException(a.getPositionDescription() + 731 ": <bitmap> requires a valid src attribute"); 732 } 733 734 state.mBitmap = bitmap; 735 } 736 737 state.mTargetDensity = r.getDisplayMetrics().densityDpi; 738 739 final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; 740 setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); 741 742 state.mAutoMirrored = a.getBoolean( 743 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); 744 state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); 745 746 final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); 747 if (tintMode != -1) { 748 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 749 } 750 751 final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); 752 if (tint != null) { 753 state.mTint = tint; 754 } 755 756 final Paint paint = mBitmapState.mPaint; 757 paint.setAntiAlias(a.getBoolean( 758 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); 759 paint.setFilterBitmap(a.getBoolean( 760 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); 761 paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); 762 763 setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); 764 765 final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); 766 if (tileMode != TILE_MODE_UNDEFINED) { 767 final Shader.TileMode mode = parseTileMode(tileMode); 768 setTileModeXY(mode, mode); 769 } 770 771 final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); 772 if (tileModeX != TILE_MODE_UNDEFINED) { 773 setTileModeX(parseTileMode(tileModeX)); 774 } 775 776 final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); 777 if (tileModeY != TILE_MODE_UNDEFINED) { 778 setTileModeY(parseTileMode(tileModeY)); 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 static Shader.TileMode parseTileMode(int tileMode) { 805 switch (tileMode) { 806 case TILE_MODE_CLAMP: 807 return Shader.TileMode.CLAMP; 808 case TILE_MODE_REPEAT: 809 return Shader.TileMode.REPEAT; 810 case TILE_MODE_MIRROR: 811 return Shader.TileMode.MIRROR; 812 default: 813 return null; 814 } 815 } 816 817 @Override 818 public boolean canApplyTheme() { 819 return mBitmapState != null && mBitmapState.mThemeAttrs != null; 820 } 821 822 @Override 823 public int getIntrinsicWidth() { 824 return mBitmapWidth; 825 } 826 827 @Override 828 public int getIntrinsicHeight() { 829 return mBitmapHeight; 830 } 831 832 @Override 833 public int getOpacity() { 834 if (mBitmapState.mGravity != Gravity.FILL) { 835 return PixelFormat.TRANSLUCENT; 836 } 837 838 final Bitmap bitmap = mBitmapState.mBitmap; 839 return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 840 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 841 } 842 843 @Override 844 public final ConstantState getConstantState() { 845 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 846 return mBitmapState; 847 } 848 849 final static class BitmapState extends ConstantState { 850 final Paint mPaint; 851 852 // Values loaded during inflation. 853 int[] mThemeAttrs = null; 854 Bitmap mBitmap = null; 855 ColorStateList mTint = null; 856 Mode mTintMode = Mode.SRC_IN; 857 int mGravity = Gravity.FILL; 858 float mBaseAlpha = 1.0f; 859 Shader.TileMode mTileModeX = null; 860 Shader.TileMode mTileModeY = null; 861 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 862 boolean mAutoMirrored = false; 863 864 int mChangingConfigurations; 865 boolean mRebuildShader; 866 867 BitmapState(Bitmap bitmap) { 868 mBitmap = bitmap; 869 mPaint = new Paint(DEFAULT_PAINT_FLAGS); 870 } 871 872 BitmapState(BitmapState bitmapState) { 873 mBitmap = bitmapState.mBitmap; 874 mTint = bitmapState.mTint; 875 mTintMode = bitmapState.mTintMode; 876 mThemeAttrs = bitmapState.mThemeAttrs; 877 mChangingConfigurations = bitmapState.mChangingConfigurations; 878 mGravity = bitmapState.mGravity; 879 mTileModeX = bitmapState.mTileModeX; 880 mTileModeY = bitmapState.mTileModeY; 881 mTargetDensity = bitmapState.mTargetDensity; 882 mBaseAlpha = bitmapState.mBaseAlpha; 883 mPaint = new Paint(bitmapState.mPaint); 884 mRebuildShader = bitmapState.mRebuildShader; 885 mAutoMirrored = bitmapState.mAutoMirrored; 886 } 887 888 @Override 889 public boolean canApplyTheme() { 890 return mThemeAttrs != null; 891 } 892 893 @Override 894 public Bitmap getBitmap() { 895 return mBitmap; 896 } 897 898 @Override 899 public Drawable newDrawable() { 900 return new BitmapDrawable(this, null, null); 901 } 902 903 @Override 904 public Drawable newDrawable(Resources res) { 905 return new BitmapDrawable(this, res, null); 906 } 907 908 @Override 909 public Drawable newDrawable(Resources res, Theme theme) { 910 return new BitmapDrawable(this, res, theme); 911 } 912 913 @Override 914 public int getChangingConfigurations() { 915 return mChangingConfigurations; 916 } 917 } 918 919 /** 920 * The one constructor to rule them all. This is called by all public 921 * constructors to set the state and initialize local properties. 922 */ 923 private BitmapDrawable(BitmapState state, Resources res, Theme theme) { 924 if (theme != null && state.canApplyTheme()) { 925 // If we need to apply a theme, implicitly mutate. 926 mBitmapState = new BitmapState(state); 927 applyTheme(theme); 928 } else { 929 mBitmapState = state; 930 } 931 932 initializeWithState(state, res); 933 } 934 935 /** 936 * Initializes local dynamic properties from state. This should be called 937 * after significant state changes, e.g. from the One True Constructor and 938 * after inflating or applying a theme. 939 */ 940 private void initializeWithState(BitmapState state, Resources res) { 941 if (res != null) { 942 mTargetDensity = res.getDisplayMetrics().densityDpi; 943 } else { 944 mTargetDensity = state.mTargetDensity; 945 } 946 947 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 948 computeBitmapSize(); 949 } 950} 951