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