BitmapDrawable.java revision 17f83df9604ef9239694e8fd5a9efb894fd28453
1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics.drawable; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.graphics.Canvas; 24import android.graphics.ColorFilter; 25import android.graphics.Paint; 26import android.graphics.PixelFormat; 27import android.graphics.Rect; 28import android.graphics.Shader; 29import android.graphics.BitmapShader; 30import android.util.AttributeSet; 31import android.util.DisplayMetrics; 32import android.view.Gravity; 33 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36 37import java.io.IOException; 38 39/** 40 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a 41 * BitmapDrawable from a file path, an input stream, through XML inflation, or from 42 * a {@link android.graphics.Bitmap} object. 43 * <p>It can be defined in an XML file with the <code><bitmap></code> element. For more 44 * information, see the guide to <a 45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 46 * <p> 47 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 48 * transformation of raw bitmap graphics, and should be used when drawing to a 49 * {@link android.graphics.Canvas}. 50 * </p> 51 * 52 * @attr ref android.R.styleable#BitmapDrawable_src 53 * @attr ref android.R.styleable#BitmapDrawable_antialias 54 * @attr ref android.R.styleable#BitmapDrawable_filter 55 * @attr ref android.R.styleable#BitmapDrawable_dither 56 * @attr ref android.R.styleable#BitmapDrawable_gravity 57 * @attr ref android.R.styleable#BitmapDrawable_tileMode 58 */ 59public class BitmapDrawable extends Drawable { 60 61 private static final int DEFAULT_PAINT_FLAGS = 62 Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; 63 private BitmapState mBitmapState; 64 private Bitmap mBitmap; 65 private int mTargetDensity; 66 67 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 68 69 private boolean mApplyGravity; 70 private boolean mMutated; 71 72 // These are scaled to match the target density. 73 private int mBitmapWidth; 74 private int mBitmapHeight; 75 76 /** 77 * Create an empty drawable, not dealing with density. 78 * @deprecated Use {@link #BitmapDrawable(Resources)} to ensure 79 * that the drawable has correctly set its target density. 80 */ 81 @Deprecated 82 public BitmapDrawable() { 83 mBitmapState = new BitmapState((Bitmap) null); 84 } 85 86 /** 87 * Create an empty drawable, setting initial target density based on 88 * the display metrics of the resources. 89 */ 90 @SuppressWarnings({"UnusedParameters"}) 91 public BitmapDrawable(Resources res) { 92 mBitmapState = new BitmapState((Bitmap) null); 93 mBitmapState.mTargetDensity = mTargetDensity; 94 } 95 96 /** 97 * Create drawable from a bitmap, not dealing with density. 98 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 99 * that the drawable has correctly set its target density. 100 */ 101 @Deprecated 102 public BitmapDrawable(Bitmap bitmap) { 103 this(new BitmapState(bitmap), null); 104 } 105 106 /** 107 * Create drawable from a bitmap, setting initial target density based on 108 * the display metrics of the resources. 109 */ 110 public BitmapDrawable(Resources res, Bitmap bitmap) { 111 this(new BitmapState(bitmap), res); 112 mBitmapState.mTargetDensity = mTargetDensity; 113 } 114 115 /** 116 * Create a drawable by opening a given file path and decoding the bitmap. 117 * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure 118 * that the drawable has correctly set its target density. 119 */ 120 @Deprecated 121 public BitmapDrawable(String filepath) { 122 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 123 if (mBitmap == null) { 124 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 125 } 126 } 127 128 /** 129 * Create a drawable by opening a given file path and decoding the bitmap. 130 */ 131 @SuppressWarnings({"UnusedParameters"}) 132 public BitmapDrawable(Resources res, String filepath) { 133 this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); 134 mBitmapState.mTargetDensity = mTargetDensity; 135 if (mBitmap == null) { 136 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 137 } 138 } 139 140 /** 141 * Create a drawable by decoding a bitmap from the given input stream. 142 * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure 143 * that the drawable has correctly set its target density. 144 */ 145 @Deprecated 146 public BitmapDrawable(java.io.InputStream is) { 147 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 148 if (mBitmap == null) { 149 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 150 } 151 } 152 153 /** 154 * Create a drawable by decoding a bitmap from the given input stream. 155 */ 156 @SuppressWarnings({"UnusedParameters"}) 157 public BitmapDrawable(Resources res, java.io.InputStream is) { 158 this(new BitmapState(BitmapFactory.decodeStream(is)), null); 159 mBitmapState.mTargetDensity = mTargetDensity; 160 if (mBitmap == null) { 161 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 162 } 163 } 164 165 /** 166 * Returns the paint used to render this drawable. 167 */ 168 public final Paint getPaint() { 169 return mBitmapState.mPaint; 170 } 171 172 /** 173 * Returns the bitmap used by this drawable to render. May be null. 174 */ 175 public final Bitmap getBitmap() { 176 return mBitmap; 177 } 178 179 private void computeBitmapSize() { 180 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 181 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 182 } 183 184 private void setBitmap(Bitmap bitmap) { 185 if (bitmap != mBitmap) { 186 mBitmap = bitmap; 187 if (bitmap != null) { 188 computeBitmapSize(); 189 } else { 190 mBitmapWidth = mBitmapHeight = -1; 191 } 192 invalidateSelf(); 193 } 194 } 195 196 /** 197 * Set the density scale at which this drawable will be rendered. This 198 * method assumes the drawable will be rendered at the same density as the 199 * specified canvas. 200 * 201 * @param canvas The Canvas from which the density scale must be obtained. 202 * 203 * @see android.graphics.Bitmap#setDensity(int) 204 * @see android.graphics.Bitmap#getDensity() 205 */ 206 public void setTargetDensity(Canvas canvas) { 207 setTargetDensity(canvas.getDensity()); 208 } 209 210 /** 211 * Set the density scale at which this drawable will be rendered. 212 * 213 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 214 * 215 * @see android.graphics.Bitmap#setDensity(int) 216 * @see android.graphics.Bitmap#getDensity() 217 */ 218 public void setTargetDensity(DisplayMetrics metrics) { 219 setTargetDensity(metrics.densityDpi); 220 } 221 222 /** 223 * Set the density at which this drawable will be rendered. 224 * 225 * @param density The density scale for this drawable. 226 * 227 * @see android.graphics.Bitmap#setDensity(int) 228 * @see android.graphics.Bitmap#getDensity() 229 */ 230 public void setTargetDensity(int density) { 231 if (mTargetDensity != density) { 232 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 233 if (mBitmap != null) { 234 computeBitmapSize(); 235 } 236 invalidateSelf(); 237 } 238 } 239 240 /** Get the gravity used to position/stretch the bitmap within its bounds. 241 * See android.view.Gravity 242 * @return the gravity applied to the bitmap 243 */ 244 public int getGravity() { 245 return mBitmapState.mGravity; 246 } 247 248 /** Set the gravity used to position/stretch the bitmap within its bounds. 249 See android.view.Gravity 250 * @param gravity the gravity 251 */ 252 public void setGravity(int gravity) { 253 if (mBitmapState.mGravity != gravity) { 254 mBitmapState.mGravity = gravity; 255 mApplyGravity = true; 256 invalidateSelf(); 257 } 258 } 259 260 /** 261 * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects 262 * the edges of the bitmap only so it applies only when the drawable is rotated. 263 * 264 * @param aa True if the bitmap should be anti-aliased, false otherwise. 265 */ 266 public void setAntiAlias(boolean aa) { 267 mBitmapState.mPaint.setAntiAlias(aa); 268 invalidateSelf(); 269 } 270 271 @Override 272 public void setFilterBitmap(boolean filter) { 273 mBitmapState.mPaint.setFilterBitmap(filter); 274 invalidateSelf(); 275 } 276 277 @Override 278 public void setDither(boolean dither) { 279 mBitmapState.mPaint.setDither(dither); 280 invalidateSelf(); 281 } 282 283 /** 284 * Indicates the repeat behavior of this drawable on the X axis. 285 * 286 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 287 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 288 */ 289 public Shader.TileMode getTileModeX() { 290 return mBitmapState.mTileModeX; 291 } 292 293 /** 294 * Indicates the repeat behavior of this drawable on the Y axis. 295 * 296 * @return {@link Shader.TileMode#CLAMP} if the bitmap does not repeat, 297 * {@link Shader.TileMode#REPEAT} or {@link Shader.TileMode#MIRROR} otherwise. 298 */ 299 public Shader.TileMode getTileModeY() { 300 return mBitmapState.mTileModeY; 301 } 302 303 /** 304 * Sets the repeat behavior of this drawable on the X axis. By default, the drawable 305 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 306 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 307 * is smaller than this drawable. 308 * 309 * @param mode The repeat mode for this drawable. 310 * 311 * @see #setTileModeY(android.graphics.Shader.TileMode) 312 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 313 */ 314 public void setTileModeX(Shader.TileMode mode) { 315 setTileModeXY(mode, mBitmapState.mTileModeY); 316 } 317 318 /** 319 * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable 320 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 321 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 322 * is smaller than this drawable. 323 * 324 * @param mode The repeat mode for this drawable. 325 * 326 * @see #setTileModeX(android.graphics.Shader.TileMode) 327 * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) 328 */ 329 public final void setTileModeY(Shader.TileMode mode) { 330 setTileModeXY(mBitmapState.mTileModeX, mode); 331 } 332 333 /** 334 * Sets the repeat behavior of this drawable on both axis. By default, the drawable 335 * does not repeat its bitmap. Using {@link Shader.TileMode#REPEAT} or 336 * {@link Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) if the bitmap 337 * is smaller than this drawable. 338 * 339 * @param xmode The X repeat mode for this drawable. 340 * @param ymode The Y repeat mode for this drawable. 341 * 342 * @see #setTileModeX(android.graphics.Shader.TileMode) 343 * @see #setTileModeY(android.graphics.Shader.TileMode) 344 */ 345 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 346 final BitmapState state = mBitmapState; 347 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 348 state.mTileModeX = xmode; 349 state.mTileModeY = ymode; 350 state.mRebuildShader = true; 351 invalidateSelf(); 352 } 353 } 354 355 @Override 356 public int getChangingConfigurations() { 357 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 358 } 359 360 @Override 361 protected void onBoundsChange(Rect bounds) { 362 super.onBoundsChange(bounds); 363 mApplyGravity = true; 364 } 365 366 @Override 367 public void draw(Canvas canvas) { 368 Bitmap bitmap = mBitmap; 369 if (bitmap != null) { 370 final BitmapState state = mBitmapState; 371 if (state.mRebuildShader) { 372 Shader.TileMode tmx = state.mTileModeX; 373 Shader.TileMode tmy = state.mTileModeY; 374 375 if (tmx == null && tmy == null) { 376 state.mPaint.setShader(null); 377 } else { 378 state.mPaint.setShader(new BitmapShader(bitmap, 379 tmx == null ? Shader.TileMode.CLAMP : tmx, 380 tmy == null ? Shader.TileMode.CLAMP : tmy)); 381 } 382 state.mRebuildShader = false; 383 copyBounds(mDstRect); 384 } 385 386 Shader shader = state.mPaint.getShader(); 387 if (shader == null) { 388 if (mApplyGravity) { 389 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 390 getBounds(), mDstRect); 391 mApplyGravity = false; 392 } 393 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 394 } else { 395 if (mApplyGravity) { 396 copyBounds(mDstRect); 397 mApplyGravity = false; 398 } 399 canvas.drawRect(mDstRect, state.mPaint); 400 } 401 } 402 } 403 404 @Override 405 public void setAlpha(int alpha) { 406 mBitmapState.mPaint.setAlpha(alpha); 407 invalidateSelf(); 408 } 409 410 @Override 411 public void setColorFilter(ColorFilter cf) { 412 mBitmapState.mPaint.setColorFilter(cf); 413 invalidateSelf(); 414 } 415 416 /** 417 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 418 * that comes from the same resource. 419 * 420 * @return This drawable. 421 */ 422 @Override 423 public Drawable mutate() { 424 if (!mMutated && super.mutate() == this) { 425 mBitmapState = new BitmapState(mBitmapState); 426 mMutated = true; 427 } 428 return this; 429 } 430 431 @Override 432 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 433 throws XmlPullParserException, IOException { 434 super.inflate(r, parser, attrs); 435 436 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 437 438 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 439 if (id == 0) { 440 throw new XmlPullParserException(parser.getPositionDescription() + 441 ": <bitmap> requires a valid src attribute"); 442 } 443 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 444 if (bitmap == null) { 445 throw new XmlPullParserException(parser.getPositionDescription() + 446 ": <bitmap> requires a valid src attribute"); 447 } 448 mBitmapState.mBitmap = bitmap; 449 setBitmap(bitmap); 450 setTargetDensity(r.getDisplayMetrics()); 451 452 final Paint paint = mBitmapState.mPaint; 453 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 454 paint.isAntiAlias())); 455 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 456 paint.isFilterBitmap())); 457 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 458 paint.isDither())); 459 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 460 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 461 if (tileMode != -1) { 462 switch (tileMode) { 463 case 0: 464 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 465 break; 466 case 1: 467 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 468 break; 469 case 2: 470 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 471 break; 472 } 473 } 474 475 a.recycle(); 476 } 477 478 @Override 479 public int getIntrinsicWidth() { 480 return mBitmapWidth; 481 } 482 483 @Override 484 public int getIntrinsicHeight() { 485 return mBitmapHeight; 486 } 487 488 @Override 489 public int getOpacity() { 490 if (mBitmapState.mGravity != Gravity.FILL) { 491 return PixelFormat.TRANSLUCENT; 492 } 493 Bitmap bm = mBitmap; 494 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 495 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 496 } 497 498 @Override 499 public final ConstantState getConstantState() { 500 mBitmapState.mChangingConfigurations = getChangingConfigurations(); 501 return mBitmapState; 502 } 503 504 final static class BitmapState extends ConstantState { 505 Bitmap mBitmap; 506 int mChangingConfigurations; 507 int mGravity = Gravity.FILL; 508 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 509 Shader.TileMode mTileModeX = null; 510 Shader.TileMode mTileModeY = null; 511 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 512 boolean mRebuildShader; 513 514 BitmapState(Bitmap bitmap) { 515 mBitmap = bitmap; 516 } 517 518 BitmapState(BitmapState bitmapState) { 519 this(bitmapState.mBitmap); 520 mChangingConfigurations = bitmapState.mChangingConfigurations; 521 mGravity = bitmapState.mGravity; 522 mTileModeX = bitmapState.mTileModeX; 523 mTileModeY = bitmapState.mTileModeY; 524 mTargetDensity = bitmapState.mTargetDensity; 525 mPaint = new Paint(bitmapState.mPaint); 526 mRebuildShader = bitmapState.mRebuildShader; 527 } 528 529 @Override 530 public Drawable newDrawable() { 531 return new BitmapDrawable(this, null); 532 } 533 534 @Override 535 public Drawable newDrawable(Resources res) { 536 return new BitmapDrawable(this, res); 537 } 538 539 @Override 540 public int getChangingConfigurations() { 541 return mChangingConfigurations; 542 } 543 } 544 545 private BitmapDrawable(BitmapState state, Resources res) { 546 mBitmapState = state; 547 if (res != null) { 548 mTargetDensity = res.getDisplayMetrics().densityDpi; 549 } else { 550 mTargetDensity = state.mTargetDensity; 551 } 552 setBitmap(state != null ? state.mBitmap : null); 553 } 554} 555