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     /** @hide */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    345     public boolean hasAntiAlias() {
    346         return mBitmapState.mPaint.isAntiAlias();
    347     }
    348 
    349     @Override
    350     public void setFilterBitmap(boolean filter) {
    351         mBitmapState.mPaint.setFilterBitmap(filter);
    352         invalidateSelf();
    353     }
    354 
    355     @Override
    356     public boolean isFilterBitmap() {
    357         return mBitmapState.mPaint.isFilterBitmap();
    358     }
    359 
    360     @Override
    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      */
    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      */
    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      */
    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      */
    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      */
    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
    444     public void setAutoMirrored(boolean mirrored) {
    445         if (mBitmapState.mAutoMirrored != mirrored) {
    446             mBitmapState.mAutoMirrored = mirrored;
    447             invalidateSelf();
    448         }
    449     }
    450 
    451     @Override
    452     public final boolean isAutoMirrored() {
    453         return mBitmapState.mAutoMirrored;
    454     }
    455 
    456     @Override
    457     public int getChangingConfigurations() {
    458         return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations();
    459     }
    460 
    461     private boolean needMirroring() {
    462         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
    463     }
    464 
    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
    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
    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 
    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
    600     public Insets getOpticalInsets() {
    601         updateDstRectAndInsetsIfDirty();
    602         return mOpticalInsets;
    603     }
    604 
    605     @Override
    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
    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
    627     public int getAlpha() {
    628         return mBitmapState.mPaint.getAlpha();
    629     }
    630 
    631     @Override
    632     public void setColorFilter(ColorFilter colorFilter) {
    633         mBitmapState.mPaint.setColorFilter(colorFilter);
    634         invalidateSelf();
    635     }
    636 
    637     @Override
    638     public ColorFilter getColorFilter() {
    639         return mBitmapState.mPaint.getColorFilter();
    640     }
    641 
    642     @Override
    643     public void setTintList(ColorStateList tint) {
    644         mBitmapState.mTint = tint;
    645         mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode);
    646         invalidateSelf();
    647     }
    648 
    649     @Override
    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      */
    659     public ColorStateList getTint() {
    660         return mBitmapState.mTint;
    661     }
    662 
    663     /**
    664      * @hide only needed by a hack within ProgressBar
    665      */
    666     public Mode getTintMode() {
    667         return mBitmapState.mTintMode;
    668     }
    669 
    670     /**
    671      * @hide Candidate for future API inclusion
    672      */
    673     @Override
    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
    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      */
    697     public void clearMutated() {
    698         super.clearMutated();
    699         mMutated = false;
    700     }
    701 
    702     @Override
    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
    713     public boolean isStateful() {
    714         return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
    715                 || super.isStateful();
    716     }
    717 
    718     @Override
    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      */
    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      */
    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
    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 
    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
    862     public boolean canApplyTheme() {
    863         return mBitmapState != null && mBitmapState.canApplyTheme();
    864     }
    865 
    866     @Override
    867     public int getIntrinsicWidth() {
    868         return mBitmapWidth;
    869     }
    870 
    871     @Override
    872     public int getIntrinsicHeight() {
    873         return mBitmapHeight;
    874     }
    875 
    876     @Override
    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
    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 
    911         BitmapState(Bitmap bitmap) {
    912             mBitmap = bitmap;
    913             mPaint = new Paint(DEFAULT_PAINT_FLAGS);
    914         }
    915 
    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
    933         public boolean canApplyTheme() {
    934             return mThemeAttrs != null || mTint != null && mTint.canApplyTheme();
    935         }
    936 
    937         @Override
    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
    946         public Drawable newDrawable() {
    947             return new BitmapDrawable(this, null);
    948         }
    949 
    950         @Override
    951         public Drawable newDrawable(Resources res) {
    952             return new BitmapDrawable(this, res);
    953         }
    954 
    955         @Override
    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      */
    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      */
    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