Home | History | Annotate | Download | only in drawable
      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
    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
    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
    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      */
    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
    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")
    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
    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")
    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      */
    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      */
    208     public final Bitmap getBitmap() {
    209         return mBitmapState.mBitmap;
    210     }
    211 
    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     private void setBitmap(Bitmap bitmap) {
    223         if (mBitmapState.mBitmap != bitmap) {
    224             mBitmapState.mBitmap = bitmap;
    225             computeBitmapSize();
    226             invalidateSelf();
    227         }
    228     }
    229 
    230     /**
    231      * Set the density scale at which this drawable will be rendered. This
    232      * method assumes the drawable will be rendered at the same density as the
    233      * specified canvas.
    234      *
    235      * @param canvas The Canvas from which the density scale must be obtained.
    236      *
    237      * @see android.graphics.Bitmap#setDensity(int)
    238      * @see android.graphics.Bitmap#getDensity()
    239      */
    240     public void setTargetDensity(Canvas canvas) {
    241         setTargetDensity(canvas.getDensity());
    242     }
    243 
    244     /**
    245      * Set the density scale at which this drawable will be rendered.
    246      *
    247      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
    248      *
    249      * @see android.graphics.Bitmap#setDensity(int)
    250      * @see android.graphics.Bitmap#getDensity()
    251      */
    252     public void setTargetDensity(DisplayMetrics metrics) {
    253         setTargetDensity(metrics.densityDpi);
    254     }
    255 
    256     /**
    257      * Set the density at which this drawable will be rendered.
    258      *
    259      * @param density The density scale for this drawable.
    260      *
    261      * @see android.graphics.Bitmap#setDensity(int)
    262      * @see android.graphics.Bitmap#getDensity()
    263      */
    264     public void setTargetDensity(int density) {
    265         if (mTargetDensity != density) {
    266             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
    267             if (mBitmapState.mBitmap != null) {
    268                 computeBitmapSize();
    269             }
    270             invalidateSelf();
    271         }
    272     }
    273 
    274     /** Get the gravity used to position/stretch the bitmap within its bounds.
    275      * See android.view.Gravity
    276      * @return the gravity applied to the bitmap
    277      */
    278     public int getGravity() {
    279         return mBitmapState.mGravity;
    280     }
    281 
    282     /** Set the gravity used to position/stretch the bitmap within its bounds.
    283         See android.view.Gravity
    284      * @param gravity the gravity
    285      */
    286     public void setGravity(int gravity) {
    287         if (mBitmapState.mGravity != gravity) {
    288             mBitmapState.mGravity = gravity;
    289             mDstRectAndInsetsDirty = true;
    290             invalidateSelf();
    291         }
    292     }
    293 
    294     /**
    295      * Enables or disables the mipmap hint for this drawable's bitmap.
    296      * See {@link Bitmap#setHasMipMap(boolean)} for more information.
    297      *
    298      * If the bitmap is null calling this method has no effect.
    299      *
    300      * @param mipMap True if the bitmap should use mipmaps, false otherwise.
    301      *
    302      * @see #hasMipMap()
    303      */
    304     public void setMipMap(boolean mipMap) {
    305         if (mBitmapState.mBitmap != null) {
    306             mBitmapState.mBitmap.setHasMipMap(mipMap);
    307             invalidateSelf();
    308         }
    309     }
    310 
    311     /**
    312      * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
    313      *
    314      * @return True if the mipmap hint is set, false otherwise. If the bitmap
    315      *         is null, this method always returns false.
    316      *
    317      * @see #setMipMap(boolean)
    318      * @attr ref android.R.styleable#BitmapDrawable_mipMap
    319      */
    320     public boolean hasMipMap() {
    321         return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
    322     }
    323 
    324     /**
    325      * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
    326      * the edges of the bitmap only so it applies only when the drawable is rotated.
    327      *
    328      * @param aa True if the bitmap should be anti-aliased, false otherwise.
    329      *
    330      * @see #hasAntiAlias()
    331      */
    332     public void setAntiAlias(boolean aa) {
    333         mBitmapState.mPaint.setAntiAlias(aa);
    334         invalidateSelf();
    335     }
    336 
    337     /**
    338      * Indicates whether anti-aliasing is enabled for this drawable.
    339      *
    340      * @return True if anti-aliasing is enabled, false otherwise.
    341      *
    342      * @see #setAntiAlias(boolean)
    343      */
    344     public boolean hasAntiAlias() {
    345         return mBitmapState.mPaint.isAntiAlias();
    346     }
    347 
    348     @Override
    349     public void setFilterBitmap(boolean filter) {
    350         mBitmapState.mPaint.setFilterBitmap(filter);
    351         invalidateSelf();
    352     }
    353 
    354     @Override
    355     public void setDither(boolean dither) {
    356         mBitmapState.mPaint.setDither(dither);
    357         invalidateSelf();
    358     }
    359 
    360     /**
    361      * Indicates the repeat behavior of this drawable on the X axis.
    362      *
    363      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
    364      *         {@link android.graphics.Shader.TileMode#REPEAT} or
    365      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
    366      */
    367     public Shader.TileMode getTileModeX() {
    368         return mBitmapState.mTileModeX;
    369     }
    370 
    371     /**
    372      * Indicates the repeat behavior of this drawable on the Y axis.
    373      *
    374      * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
    375      *         {@link android.graphics.Shader.TileMode#REPEAT} or
    376      *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
    377      */
    378     public Shader.TileMode getTileModeY() {
    379         return mBitmapState.mTileModeY;
    380     }
    381 
    382     /**
    383      * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
    384      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
    385      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
    386      * if the bitmap is smaller than this drawable.
    387      *
    388      * @param mode The repeat mode for this drawable.
    389      *
    390      * @see #setTileModeY(android.graphics.Shader.TileMode)
    391      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
    392      * @attr ref android.R.styleable#BitmapDrawable_tileModeX
    393      */
    394     public void setTileModeX(Shader.TileMode mode) {
    395         setTileModeXY(mode, mBitmapState.mTileModeY);
    396     }
    397 
    398     /**
    399      * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
    400      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
    401      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
    402      * if the bitmap is smaller than this drawable.
    403      *
    404      * @param mode The repeat mode for this drawable.
    405      *
    406      * @see #setTileModeX(android.graphics.Shader.TileMode)
    407      * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
    408      * @attr ref android.R.styleable#BitmapDrawable_tileModeY
    409      */
    410     public final void setTileModeY(Shader.TileMode mode) {
    411         setTileModeXY(mBitmapState.mTileModeX, mode);
    412     }
    413 
    414     /**
    415      * Sets the repeat behavior of this drawable on both axis. By default, the drawable
    416      * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
    417      * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
    418      * if the bitmap is smaller than this drawable.
    419      *
    420      * @param xmode The X repeat mode for this drawable.
    421      * @param ymode The Y repeat mode for this drawable.
    422      *
    423      * @see #setTileModeX(android.graphics.Shader.TileMode)
    424      * @see #setTileModeY(android.graphics.Shader.TileMode)
    425      */
    426     public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
    427         final BitmapState state = mBitmapState;
    428         if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
    429             state.mTileModeX = xmode;
    430             state.mTileModeY = ymode;
    431             state.mRebuildShader = true;
    432             mDstRectAndInsetsDirty = true;
    433             invalidateSelf();
    434         }
    435     }
    436 
    437     @Override
    438     public void setAutoMirrored(boolean mirrored) {
    439         if (mBitmapState.mAutoMirrored != mirrored) {
    440             mBitmapState.mAutoMirrored = mirrored;
    441             invalidateSelf();
    442         }
    443     }
    444 
    445     @Override
    446     public final boolean isAutoMirrored() {
    447         return mBitmapState.mAutoMirrored;
    448     }
    449 
    450     @Override
    451     public int getChangingConfigurations() {
    452         return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
    453     }
    454 
    455     private boolean needMirroring() {
    456         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
    457     }
    458 
    459     private void updateMirrorMatrix(float dx) {
    460         if (mMirrorMatrix == null) {
    461             mMirrorMatrix = new Matrix();
    462         }
    463         mMirrorMatrix.setTranslate(dx, 0);
    464         mMirrorMatrix.preScale(-1.0f, 1.0f);
    465     }
    466 
    467     @Override
    468     protected void onBoundsChange(Rect bounds) {
    469         mDstRectAndInsetsDirty = true;
    470 
    471         final Shader shader = mBitmapState.mPaint.getShader();
    472         if (shader != null) {
    473             if (needMirroring()) {
    474                 updateMirrorMatrix(bounds.right - bounds.left);
    475                 shader.setLocalMatrix(mMirrorMatrix);
    476                 mBitmapState.mPaint.setShader(shader);
    477             } else {
    478                 if (mMirrorMatrix != null) {
    479                     mMirrorMatrix = null;
    480                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
    481                     mBitmapState.mPaint.setShader(shader);
    482                 }
    483             }
    484         }
    485     }
    486 
    487     @Override
    488     public void draw(Canvas canvas) {
    489         final Bitmap bitmap = mBitmapState.mBitmap;
    490         if (bitmap == null) {
    491             return;
    492         }
    493 
    494         final BitmapState state = mBitmapState;
    495         final Paint paint = state.mPaint;
    496         if (state.mRebuildShader) {
    497             final Shader.TileMode tmx = state.mTileModeX;
    498             final Shader.TileMode tmy = state.mTileModeY;
    499             if (tmx == null && tmy == null) {
    500                 paint.setShader(null);
    501             } else {
    502                 paint.setShader(new BitmapShader(bitmap,
    503                         tmx == null ? Shader.TileMode.CLAMP : tmx,
    504                         tmy == null ? Shader.TileMode.CLAMP : tmy));
    505             }
    506 
    507             state.mRebuildShader = false;
    508         }
    509 
    510         final int restoreAlpha;
    511         if (state.mBaseAlpha != 1.0f) {
    512             final Paint p = getPaint();
    513             restoreAlpha = p.getAlpha();
    514             p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
    515         } else {
    516             restoreAlpha = -1;
    517         }
    518 
    519         final boolean clearColorFilter;
    520         if (mTintFilter != null && paint.getColorFilter() == null) {
    521             paint.setColorFilter(mTintFilter);
    522             clearColorFilter = true;
    523         } else {
    524             clearColorFilter = false;
    525         }
    526 
    527         updateDstRectAndInsetsIfDirty();
    528         final Shader shader = paint.getShader();
    529         final boolean needMirroring = needMirroring();
    530         if (shader == null) {
    531             if (needMirroring) {
    532                 canvas.save();
    533                 // Mirror the bitmap
    534                 canvas.translate(mDstRect.right - mDstRect.left, 0);
    535                 canvas.scale(-1.0f, 1.0f);
    536             }
    537 
    538             canvas.drawBitmap(bitmap, null, mDstRect, paint);
    539 
    540             if (needMirroring) {
    541                 canvas.restore();
    542             }
    543         } else {
    544             if (needMirroring) {
    545                 // Mirror the bitmap
    546                 updateMirrorMatrix(mDstRect.right - mDstRect.left);
    547                 shader.setLocalMatrix(mMirrorMatrix);
    548                 paint.setShader(shader);
    549             } else {
    550                 if (mMirrorMatrix != null) {
    551                     mMirrorMatrix = null;
    552                     shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
    553                     paint.setShader(shader);
    554                 }
    555             }
    556 
    557             canvas.drawRect(mDstRect, paint);
    558         }
    559 
    560         if (clearColorFilter) {
    561             paint.setColorFilter(null);
    562         }
    563 
    564         if (restoreAlpha >= 0) {
    565             paint.setAlpha(restoreAlpha);
    566         }
    567     }
    568 
    569     private void updateDstRectAndInsetsIfDirty() {
    570         if (mDstRectAndInsetsDirty) {
    571             if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) {
    572                 final Rect bounds = getBounds();
    573                 final int layoutDirection = getLayoutDirection();
    574                 Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
    575                         bounds, mDstRect, layoutDirection);
    576 
    577                 final int left = mDstRect.left - bounds.left;
    578                 final int top = mDstRect.top - bounds.top;
    579                 final int right = bounds.right - mDstRect.right;
    580                 final int bottom = bounds.bottom - mDstRect.bottom;
    581                 mOpticalInsets = Insets.of(left, top, right, bottom);
    582             } else {
    583                 copyBounds(mDstRect);
    584                 mOpticalInsets = Insets.NONE;
    585             }
    586         }
    587         mDstRectAndInsetsDirty = false;
    588     }
    589 
    590     /**
    591      * @hide
    592      */
    593     @Override
    594     public Insets getOpticalInsets() {
    595         updateDstRectAndInsetsIfDirty();
    596         return mOpticalInsets;
    597     }
    598 
    599     @Override
    600     public void getOutline(@NonNull Outline outline) {
    601         updateDstRectAndInsetsIfDirty();
    602         outline.setRect(mDstRect);
    603 
    604         // Only opaque Bitmaps can report a non-0 alpha,
    605         // since only they are guaranteed to fill their bounds
    606         boolean opaqueOverShape = mBitmapState.mBitmap != null
    607                 && !mBitmapState.mBitmap.hasAlpha();
    608         outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f);
    609     }
    610 
    611     @Override
    612     public void setAlpha(int alpha) {
    613         final int oldAlpha = mBitmapState.mPaint.getAlpha();
    614         if (alpha != oldAlpha) {
    615             mBitmapState.mPaint.setAlpha(alpha);
    616             invalidateSelf();
    617         }
    618     }
    619 
    620     @Override
    621     public int getAlpha() {
    622         return mBitmapState.mPaint.getAlpha();
    623     }
    624 
    625     @Override
    626     public void setColorFilter(ColorFilter cf) {
    627         mBitmapState.mPaint.setColorFilter(cf);
    628         invalidateSelf();
    629     }
    630 
    631     @Override
    632     public ColorFilter getColorFilter() {
    633         return mBitmapState.mPaint.getColorFilter();
    634     }
    635 
    636     @Override
    637     public void setTintList(ColorStateList tint) {
    638         mBitmapState.mTint = tint;
    639         mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
    640         invalidateSelf();
    641     }
    642 
    643     @Override
    644     public void setTintMode(PorterDuff.Mode tintMode) {
    645         mBitmapState.mTintMode = tintMode;
    646         mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode);
    647         invalidateSelf();
    648     }
    649 
    650     /**
    651      * @hide only needed by a hack within ProgressBar
    652      */
    653     public ColorStateList getTint() {
    654         return mBitmapState.mTint;
    655     }
    656 
    657     /**
    658      * @hide only needed by a hack within ProgressBar
    659      */
    660     public Mode getTintMode() {
    661         return mBitmapState.mTintMode;
    662     }
    663 
    664     /**
    665      * @hide Candidate for future API inclusion
    666      */
    667     @Override
    668     public void setXfermode(Xfermode xfermode) {
    669         mBitmapState.mPaint.setXfermode(xfermode);
    670         invalidateSelf();
    671     }
    672 
    673     /**
    674      * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
    675      * that comes from the same resource.
    676      *
    677      * @return This drawable.
    678      */
    679     @Override
    680     public Drawable mutate() {
    681         if (!mMutated && super.mutate() == this) {
    682             mBitmapState = new BitmapState(mBitmapState);
    683             mMutated = true;
    684         }
    685         return this;
    686     }
    687 
    688     /**
    689      * @hide
    690      */
    691     public void clearMutated() {
    692         super.clearMutated();
    693         mMutated = false;
    694     }
    695 
    696     @Override
    697     protected boolean onStateChange(int[] stateSet) {
    698         final BitmapState state = mBitmapState;
    699         if (state.mTint != null && state.mTintMode != null) {
    700             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    701             return true;
    702         }
    703         return false;
    704     }
    705 
    706     @Override
    707     public boolean isStateful() {
    708         final BitmapState s = mBitmapState;
    709         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
    710     }
    711 
    712     @Override
    713     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
    714             throws XmlPullParserException, IOException {
    715         super.inflate(r, parser, attrs, theme);
    716 
    717         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
    718         updateStateFromTypedArray(a);
    719         verifyState(a);
    720         a.recycle();
    721     }
    722 
    723     /**
    724      * Ensures all required attributes are set.
    725      *
    726      * @throws XmlPullParserException if any required attributes are missing
    727      */
    728     private void verifyState(TypedArray a) throws XmlPullParserException {
    729         final BitmapState state = mBitmapState;
    730         if (state.mBitmap == null) {
    731             throw new XmlPullParserException(a.getPositionDescription() +
    732                     ": <bitmap> requires a valid src attribute");
    733         }
    734     }
    735 
    736     /**
    737      * Updates the constant state from the values in the typed array.
    738      */
    739     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
    740         final Resources r = a.getResources();
    741         final BitmapState state = mBitmapState;
    742 
    743         // Account for any configuration changes.
    744         state.mChangingConfigurations |= a.getChangingConfigurations();
    745 
    746         // Extract the theme attributes, if any.
    747         state.mThemeAttrs = a.extractThemeAttrs();
    748 
    749         final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
    750         if (srcResId != 0) {
    751             final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
    752             if (bitmap == null) {
    753                 throw new XmlPullParserException(a.getPositionDescription() +
    754                         ": <bitmap> requires a valid src attribute");
    755             }
    756 
    757             state.mBitmap = bitmap;
    758         }
    759 
    760         state.mTargetDensity = r.getDisplayMetrics().densityDpi;
    761 
    762         final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
    763         setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
    764 
    765         state.mAutoMirrored = a.getBoolean(
    766                 R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
    767         state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
    768 
    769         final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
    770         if (tintMode != -1) {
    771             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
    772         }
    773 
    774         final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
    775         if (tint != null) {
    776             state.mTint = tint;
    777         }
    778 
    779         final Paint paint = mBitmapState.mPaint;
    780         paint.setAntiAlias(a.getBoolean(
    781                 R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
    782         paint.setFilterBitmap(a.getBoolean(
    783                 R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
    784         paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
    785 
    786         setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
    787 
    788         final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
    789         if (tileMode != TILE_MODE_UNDEFINED) {
    790             final Shader.TileMode mode = parseTileMode(tileMode);
    791             setTileModeXY(mode, mode);
    792         }
    793 
    794         final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
    795         if (tileModeX != TILE_MODE_UNDEFINED) {
    796             setTileModeX(parseTileMode(tileModeX));
    797         }
    798 
    799         final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
    800         if (tileModeY != TILE_MODE_UNDEFINED) {
    801             setTileModeY(parseTileMode(tileModeY));
    802         }
    803 
    804         // Update local properties.
    805         initializeWithState(state, r);
    806     }
    807 
    808     @Override
    809     public void applyTheme(Theme t) {
    810         super.applyTheme(t);
    811 
    812         final BitmapState state = mBitmapState;
    813         if (state == null || state.mThemeAttrs == null) {
    814             return;
    815         }
    816 
    817         final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
    818         try {
    819             updateStateFromTypedArray(a);
    820         } catch (XmlPullParserException e) {
    821             throw new RuntimeException(e);
    822         } finally {
    823             a.recycle();
    824         }
    825     }
    826 
    827     private static Shader.TileMode parseTileMode(int tileMode) {
    828         switch (tileMode) {
    829             case TILE_MODE_CLAMP:
    830                 return Shader.TileMode.CLAMP;
    831             case TILE_MODE_REPEAT:
    832                 return Shader.TileMode.REPEAT;
    833             case TILE_MODE_MIRROR:
    834                 return Shader.TileMode.MIRROR;
    835             default:
    836                 return null;
    837         }
    838     }
    839 
    840     @Override
    841     public boolean canApplyTheme() {
    842         return mBitmapState != null && mBitmapState.mThemeAttrs != null;
    843     }
    844 
    845     @Override
    846     public int getIntrinsicWidth() {
    847         return mBitmapWidth;
    848     }
    849 
    850     @Override
    851     public int getIntrinsicHeight() {
    852         return mBitmapHeight;
    853     }
    854 
    855     @Override
    856     public int getOpacity() {
    857         if (mBitmapState.mGravity != Gravity.FILL) {
    858             return PixelFormat.TRANSLUCENT;
    859         }
    860 
    861         final Bitmap bitmap = mBitmapState.mBitmap;
    862         return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
    863                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    864     }
    865 
    866     @Override
    867     public final ConstantState getConstantState() {
    868         mBitmapState.mChangingConfigurations = getChangingConfigurations();
    869         return mBitmapState;
    870     }
    871 
    872     final static class BitmapState extends ConstantState {
    873         final Paint mPaint;
    874 
    875         // Values loaded during inflation.
    876         int[] mThemeAttrs = null;
    877         Bitmap mBitmap = null;
    878         ColorStateList mTint = null;
    879         Mode mTintMode = DEFAULT_TINT_MODE;
    880         int mGravity = Gravity.FILL;
    881         float mBaseAlpha = 1.0f;
    882         Shader.TileMode mTileModeX = null;
    883         Shader.TileMode mTileModeY = null;
    884         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
    885         boolean mAutoMirrored = false;
    886 
    887         int mChangingConfigurations;
    888         boolean mRebuildShader;
    889 
    890         BitmapState(Bitmap bitmap) {
    891             mBitmap = bitmap;
    892             mPaint = new Paint(DEFAULT_PAINT_FLAGS);
    893         }
    894 
    895         BitmapState(BitmapState bitmapState) {
    896             mBitmap = bitmapState.mBitmap;
    897             mTint = bitmapState.mTint;
    898             mTintMode = bitmapState.mTintMode;
    899             mThemeAttrs = bitmapState.mThemeAttrs;
    900             mChangingConfigurations = bitmapState.mChangingConfigurations;
    901             mGravity = bitmapState.mGravity;
    902             mTileModeX = bitmapState.mTileModeX;
    903             mTileModeY = bitmapState.mTileModeY;
    904             mTargetDensity = bitmapState.mTargetDensity;
    905             mBaseAlpha = bitmapState.mBaseAlpha;
    906             mPaint = new Paint(bitmapState.mPaint);
    907             mRebuildShader = bitmapState.mRebuildShader;
    908             mAutoMirrored = bitmapState.mAutoMirrored;
    909         }
    910 
    911         @Override
    912         public boolean canApplyTheme() {
    913             return mThemeAttrs != null;
    914         }
    915 
    916         @Override
    917         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
    918             if (isAtlasable(mBitmap) && atlasList.add(mBitmap)) {
    919                 return mBitmap.getWidth() * mBitmap.getHeight();
    920             }
    921             return 0;
    922         }
    923 
    924         @Override
    925         public Drawable newDrawable() {
    926             return new BitmapDrawable(this, null);
    927         }
    928 
    929         @Override
    930         public Drawable newDrawable(Resources res) {
    931             return new BitmapDrawable(this, res);
    932         }
    933 
    934         @Override
    935         public int getChangingConfigurations() {
    936             return mChangingConfigurations;
    937         }
    938     }
    939 
    940     /**
    941      * The one constructor to rule them all. This is called by all public
    942      * constructors to set the state and initialize local properties.
    943      */
    944     private BitmapDrawable(BitmapState state, Resources res) {
    945         mBitmapState = state;
    946 
    947         initializeWithState(mBitmapState, res);
    948     }
    949 
    950     /**
    951      * Initializes local dynamic properties from state. This should be called
    952      * after significant state changes, e.g. from the One True Constructor and
    953      * after inflating or applying a theme.
    954      */
    955     private void initializeWithState(BitmapState state, Resources res) {
    956         if (res != null) {
    957             mTargetDensity = res.getDisplayMetrics().densityDpi;
    958         } else {
    959             mTargetDensity = state.mTargetDensity;
    960         }
    961 
    962         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    963         computeBitmapSize();
    964     }
    965 }
    966