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