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