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