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