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