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