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