BitmapDrawable.java revision 11ea33471e1a14a8594f0b2cd012d86340dd3bd8
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.</p> 44 * <p> 45 * Also see the {@link android.graphics.Bitmap} class, which handles the management and 46 * transformation of raw bitmap graphics, and should be used when drawing to a 47 * {@link android.graphics.Canvas}. 48 * </p> 49 * 50 * @attr ref android.R.styleable#BitmapDrawable_src 51 * @attr ref android.R.styleable#BitmapDrawable_antialias 52 * @attr ref android.R.styleable#BitmapDrawable_filter 53 * @attr ref android.R.styleable#BitmapDrawable_dither 54 * @attr ref android.R.styleable#BitmapDrawable_gravity 55 * @attr ref android.R.styleable#BitmapDrawable_tileMode 56 */ 57public class BitmapDrawable extends Drawable { 58 59 private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG; 60 private BitmapState mBitmapState; 61 private Bitmap mBitmap; 62 private final Rect mDstRect = new Rect(); // Gravity.apply() sets this 63 64 private boolean mApplyGravity; 65 private boolean mRebuildShader; 66 private boolean mMutated; 67 68 private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 69 70 // These are scaled to match the target density. 71 private int mBitmapWidth; 72 private int mBitmapHeight; 73 74 /** 75 * Create an empty drawable, not dealing with density. 76 * @deprecated Use {@link #BitmapDrawable(Resources)} to ensure 77 * that the drawable has correctly set its target density. 78 */ 79 public BitmapDrawable() { 80 mBitmapState = new BitmapState((Bitmap) null); 81 } 82 83 /** 84 * Create an empty drawable, setting initial target density based on 85 * the display metrics of the resources. 86 */ 87 public BitmapDrawable(Resources res) { 88 mBitmapState = new BitmapState((Bitmap) null); 89 if (res != null) { 90 setTargetDensity(res.getDisplayMetrics()); 91 mBitmapState.mTargetDensity = mTargetDensity; 92 } 93 } 94 95 /** 96 * Create drawable from a bitmap, not dealing with density. 97 * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure 98 * that the drawable has correctly set its target density. 99 */ 100 public BitmapDrawable(Bitmap bitmap) { 101 this(new BitmapState(bitmap)); 102 } 103 104 /** 105 * Create drawable from a bitmap, setting initial target density based on 106 * the display metrics of the resources. 107 */ 108 public BitmapDrawable(Resources res, Bitmap bitmap) { 109 this(new BitmapState(bitmap)); 110 if (res != null) { 111 setTargetDensity(res.getDisplayMetrics()); 112 mBitmapState.mTargetDensity = mTargetDensity; 113 } 114 } 115 116 public BitmapDrawable(String filepath) { 117 this(new BitmapState(BitmapFactory.decodeFile(filepath))); 118 if (mBitmap == null) { 119 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); 120 } 121 } 122 123 public BitmapDrawable(java.io.InputStream is) { 124 this(new BitmapState(BitmapFactory.decodeStream(is))); 125 if (mBitmap == null) { 126 android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); 127 } 128 } 129 130 public final Paint getPaint() { 131 return mBitmapState.mPaint; 132 } 133 134 public final Bitmap getBitmap() { 135 return mBitmap; 136 } 137 138 private void computeBitmapSize() { 139 mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); 140 mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); 141 } 142 143 private void setBitmap(Bitmap bitmap) { 144 mBitmap = bitmap; 145 if (bitmap != null) { 146 computeBitmapSize(); 147 } else { 148 mBitmapWidth = mBitmapHeight = -1; 149 } 150 } 151 152 /** 153 * Set the density scale at which this drawable will be rendered. This 154 * method assumes the drawable will be rendered at the same density as the 155 * specified canvas. 156 * 157 * @param canvas The Canvas from which the density scale must be obtained. 158 * 159 * @see android.graphics.Bitmap#setDensity(int) 160 * @see android.graphics.Bitmap#getDensity() 161 */ 162 public void setTargetDensity(Canvas canvas) { 163 setTargetDensity(canvas.getDensity()); 164 } 165 166 /** 167 * Set the density scale at which this drawable will be rendered. 168 * 169 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 170 * 171 * @see android.graphics.Bitmap#setDensity(int) 172 * @see android.graphics.Bitmap#getDensity() 173 */ 174 public void setTargetDensity(DisplayMetrics metrics) { 175 mTargetDensity = metrics.densityDpi; 176 if (mBitmap != null) { 177 computeBitmapSize(); 178 } 179 } 180 181 /** 182 * Set the density at which this drawable will be rendered. 183 * 184 * @param density The density scale for this drawable. 185 * 186 * @see android.graphics.Bitmap#setDensity(int) 187 * @see android.graphics.Bitmap#getDensity() 188 */ 189 public void setTargetDensity(int density) { 190 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 191 if (mBitmap != null) { 192 computeBitmapSize(); 193 } 194 } 195 196 /** Get the gravity used to position/stretch the bitmap within its bounds. 197 * See android.view.Gravity 198 * @return the gravity applied to the bitmap 199 */ 200 public int getGravity() { 201 return mBitmapState.mGravity; 202 } 203 204 /** Set the gravity used to position/stretch the bitmap within its bounds. 205 See android.view.Gravity 206 * @param gravity the gravity 207 */ 208 public void setGravity(int gravity) { 209 mBitmapState.mGravity = gravity; 210 mApplyGravity = true; 211 } 212 213 public void setAntiAlias(boolean aa) { 214 mBitmapState.mPaint.setAntiAlias(aa); 215 } 216 217 @Override 218 public void setFilterBitmap(boolean filter) { 219 mBitmapState.mPaint.setFilterBitmap(filter); 220 } 221 222 @Override 223 public void setDither(boolean dither) { 224 mBitmapState.mPaint.setDither(dither); 225 } 226 227 public Shader.TileMode getTileModeX() { 228 return mBitmapState.mTileModeX; 229 } 230 231 public Shader.TileMode getTileModeY() { 232 return mBitmapState.mTileModeY; 233 } 234 235 public void setTileModeX(Shader.TileMode mode) { 236 setTileModeXY(mode, mBitmapState.mTileModeY); 237 } 238 239 public final void setTileModeY(Shader.TileMode mode) { 240 setTileModeXY(mBitmapState.mTileModeX, mode); 241 } 242 243 public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { 244 final BitmapState state = mBitmapState; 245 if (state.mTileModeX != xmode || state.mTileModeY != ymode) { 246 state.mTileModeX = xmode; 247 state.mTileModeY = ymode; 248 mRebuildShader = true; 249 } 250 } 251 252 @Override 253 public int getChangingConfigurations() { 254 return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations; 255 } 256 257 @Override 258 protected void onBoundsChange(Rect bounds) { 259 super.onBoundsChange(bounds); 260 mApplyGravity = true; 261 } 262 263 @Override 264 public void draw(Canvas canvas) { 265 Bitmap bitmap = mBitmap; 266 if (bitmap != null) { 267 final BitmapState state = mBitmapState; 268 if (mRebuildShader) { 269 Shader.TileMode tmx = state.mTileModeX; 270 Shader.TileMode tmy = state.mTileModeY; 271 272 if (tmx == null && tmy == null) { 273 state.mPaint.setShader(null); 274 } else { 275 Shader s = new BitmapShader(bitmap, 276 tmx == null ? Shader.TileMode.CLAMP : tmx, 277 tmy == null ? Shader.TileMode.CLAMP : tmy); 278 state.mPaint.setShader(s); 279 } 280 mRebuildShader = false; 281 copyBounds(mDstRect); 282 } 283 284 Shader shader = state.mPaint.getShader(); 285 if (shader == null) { 286 if (mApplyGravity) { 287 Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight, 288 getBounds(), mDstRect); 289 mApplyGravity = false; 290 } 291 canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint); 292 } else { 293 if (mApplyGravity) { 294 mDstRect.set(getBounds()); 295 mApplyGravity = false; 296 } 297 canvas.drawRect(mDstRect, state.mPaint); 298 } 299 } 300 } 301 302 @Override 303 public void setAlpha(int alpha) { 304 mBitmapState.mPaint.setAlpha(alpha); 305 } 306 307 @Override 308 public void setColorFilter(ColorFilter cf) { 309 mBitmapState.mPaint.setColorFilter(cf); 310 } 311 312 /** 313 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 314 * that comes from the same resource. 315 * 316 * @return This drawable. 317 */ 318 @Override 319 public Drawable mutate() { 320 if (!mMutated && super.mutate() == this) { 321 mBitmapState = new BitmapState(mBitmapState); 322 mMutated = true; 323 } 324 return this; 325 } 326 327 @Override 328 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 329 throws XmlPullParserException, IOException { 330 super.inflate(r, parser, attrs); 331 332 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); 333 334 final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); 335 if (id == 0) { 336 throw new XmlPullParserException(parser.getPositionDescription() + 337 ": <bitmap> requires a valid src attribute"); 338 } 339 final Bitmap bitmap = BitmapFactory.decodeResource(r, id); 340 if (bitmap == null) { 341 throw new XmlPullParserException(parser.getPositionDescription() + 342 ": <bitmap> requires a valid src attribute"); 343 } 344 mBitmapState.mBitmap = bitmap; 345 setBitmap(bitmap); 346 setTargetDensity(r.getDisplayMetrics()); 347 348 final Paint paint = mBitmapState.mPaint; 349 paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, 350 paint.isAntiAlias())); 351 paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, 352 paint.isFilterBitmap())); 353 paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, 354 paint.isDither())); 355 setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); 356 int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); 357 if (tileMode != -1) { 358 switch (tileMode) { 359 case 0: 360 setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 361 break; 362 case 1: 363 setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); 364 break; 365 case 2: 366 setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); 367 break; 368 } 369 } 370 371 a.recycle(); 372 } 373 374 @Override 375 public int getIntrinsicWidth() { 376 return mBitmapWidth; 377 } 378 379 @Override 380 public int getIntrinsicHeight() { 381 return mBitmapHeight; 382 } 383 384 @Override 385 public int getOpacity() { 386 if (mBitmapState.mGravity != Gravity.FILL) { 387 return PixelFormat.TRANSLUCENT; 388 } 389 Bitmap bm = mBitmap; 390 return (bm == null || bm.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? 391 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 392 } 393 394 @Override 395 public final ConstantState getConstantState() { 396 mBitmapState.mChangingConfigurations = super.getChangingConfigurations(); 397 return mBitmapState; 398 } 399 400 final static class BitmapState extends ConstantState { 401 Bitmap mBitmap; 402 int mChangingConfigurations; 403 int mGravity = Gravity.FILL; 404 Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); 405 Shader.TileMode mTileModeX; 406 Shader.TileMode mTileModeY; 407 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 408 409 BitmapState(Bitmap bitmap) { 410 mBitmap = bitmap; 411 } 412 413 BitmapState(BitmapState bitmapState) { 414 this(bitmapState.mBitmap); 415 mChangingConfigurations = bitmapState.mChangingConfigurations; 416 mGravity = bitmapState.mGravity; 417 mTileModeX = bitmapState.mTileModeX; 418 mTileModeY = bitmapState.mTileModeY; 419 mTargetDensity = bitmapState.mTargetDensity; 420 mPaint = new Paint(bitmapState.mPaint); 421 } 422 423 @Override 424 public Drawable newDrawable() { 425 return new BitmapDrawable(this); 426 } 427 428 @Override 429 public int getChangingConfigurations() { 430 return mChangingConfigurations; 431 } 432 } 433 434 private BitmapDrawable(BitmapState state) { 435 mBitmapState = state; 436 mTargetDensity = state.mTargetDensity; 437 setBitmap(state.mBitmap); 438 } 439} 440