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