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.graphics.*;
     20 import android.content.res.Resources;
     21 import android.content.res.TypedArray;
     22 import android.util.AttributeSet;
     23 import android.util.DisplayMetrics;
     24 import android.util.TypedValue;
     25 import org.xmlpull.v1.XmlPullParser;
     26 import org.xmlpull.v1.XmlPullParserException;
     27 
     28 import java.io.IOException;
     29 import java.io.InputStream;
     30 
     31 /**
     32  *
     33  * A resizeable bitmap, with stretchable areas that you define. This type of image
     34  * is defined in a .png file with a special format.
     35  *
     36  * <div class="special reference">
     37  * <h3>Developer Guides</h3>
     38  * <p>For more information about how to use a NinePatchDrawable, read the
     39  * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
     40  * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
     41  * file using the draw9patch tool, see the
     42  * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
     43  */
     44 public class NinePatchDrawable extends Drawable {
     45     // dithering helps a lot, and is pretty cheap, so default is true
     46     private static final boolean DEFAULT_DITHER = true;
     47     private NinePatchState mNinePatchState;
     48     private NinePatch mNinePatch;
     49     private Rect mPadding;
     50     private Paint mPaint;
     51     private boolean mMutated;
     52 
     53     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
     54 
     55     // These are scaled to match the target density.
     56     private int mBitmapWidth;
     57     private int mBitmapHeight;
     58 
     59     NinePatchDrawable() {
     60     }
     61 
     62     /**
     63      * Create drawable from raw nine-patch data, not dealing with density.
     64      * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
     65      * to ensure that the drawable has correctly set its target density.
     66      */
     67     @Deprecated
     68     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
     69         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
     70     }
     71 
     72     /**
     73      * Create drawable from raw nine-patch data, setting initial target density
     74      * based on the display metrics of the resources.
     75      */
     76     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
     77             Rect padding, String srcName) {
     78         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
     79         mNinePatchState.mTargetDensity = mTargetDensity;
     80     }
     81 
     82     /**
     83      * Create drawable from existing nine-patch, not dealing with density.
     84      * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
     85      * to ensure that the drawable has correctly set its target density.
     86      */
     87     @Deprecated
     88     public NinePatchDrawable(NinePatch patch) {
     89         this(new NinePatchState(patch, new Rect()), null);
     90     }
     91 
     92     /**
     93      * Create drawable from existing nine-patch, setting initial target density
     94      * based on the display metrics of the resources.
     95      */
     96     public NinePatchDrawable(Resources res, NinePatch patch) {
     97         this(new NinePatchState(patch, new Rect()), res);
     98         mNinePatchState.mTargetDensity = mTargetDensity;
     99     }
    100 
    101     private void setNinePatchState(NinePatchState state, Resources res) {
    102         mNinePatchState = state;
    103         mNinePatch = state.mNinePatch;
    104         mPadding = state.mPadding;
    105         mTargetDensity = res != null ? res.getDisplayMetrics().densityDpi
    106                 : state.mTargetDensity;
    107         //noinspection PointlessBooleanExpression
    108         if (state.mDither != DEFAULT_DITHER) {
    109             // avoid calling the setter unless we need to, since it does a
    110             // lazy allocation of a paint
    111             setDither(state.mDither);
    112         }
    113         if (mNinePatch != null) {
    114             computeBitmapSize();
    115         }
    116     }
    117 
    118     /**
    119      * Set the density scale at which this drawable will be rendered. This
    120      * method assumes the drawable will be rendered at the same density as the
    121      * specified canvas.
    122      *
    123      * @param canvas The Canvas from which the density scale must be obtained.
    124      *
    125      * @see android.graphics.Bitmap#setDensity(int)
    126      * @see android.graphics.Bitmap#getDensity()
    127      */
    128     public void setTargetDensity(Canvas canvas) {
    129         setTargetDensity(canvas.getDensity());
    130     }
    131 
    132     /**
    133      * Set the density scale at which this drawable will be rendered.
    134      *
    135      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
    136      *
    137      * @see android.graphics.Bitmap#setDensity(int)
    138      * @see android.graphics.Bitmap#getDensity()
    139      */
    140     public void setTargetDensity(DisplayMetrics metrics) {
    141         setTargetDensity(metrics.densityDpi);
    142     }
    143 
    144     /**
    145      * Set the density at which this drawable will be rendered.
    146      *
    147      * @param density The density scale for this drawable.
    148      *
    149      * @see android.graphics.Bitmap#setDensity(int)
    150      * @see android.graphics.Bitmap#getDensity()
    151      */
    152     public void setTargetDensity(int density) {
    153         if (density != mTargetDensity) {
    154             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
    155             if (mNinePatch != null) {
    156                 computeBitmapSize();
    157             }
    158             invalidateSelf();
    159         }
    160     }
    161 
    162     private void computeBitmapSize() {
    163         final int sdensity = mNinePatch.getDensity();
    164         final int tdensity = mTargetDensity;
    165         if (sdensity == tdensity) {
    166             mBitmapWidth = mNinePatch.getWidth();
    167             mBitmapHeight = mNinePatch.getHeight();
    168         } else {
    169             mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(),
    170                     sdensity, tdensity);
    171             mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(),
    172                     sdensity, tdensity);
    173             if (mNinePatchState.mPadding != null && mPadding != null) {
    174                 Rect dest = mPadding;
    175                 Rect src = mNinePatchState.mPadding;
    176                 if (dest == src) {
    177                     mPadding = dest = new Rect(src);
    178                 }
    179                 dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
    180                 dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
    181                 dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
    182                 dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
    183             }
    184         }
    185     }
    186 
    187     @Override
    188     public void draw(Canvas canvas) {
    189         mNinePatch.draw(canvas, getBounds(), mPaint);
    190     }
    191 
    192     @Override
    193     public int getChangingConfigurations() {
    194         return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
    195     }
    196 
    197     @Override
    198     public boolean getPadding(Rect padding) {
    199         padding.set(mPadding);
    200         return true;
    201     }
    202 
    203     @Override
    204     public void setAlpha(int alpha) {
    205         if (mPaint == null && alpha == 0xFF) {
    206             // Fast common case -- leave at normal alpha.
    207             return;
    208         }
    209         getPaint().setAlpha(alpha);
    210         invalidateSelf();
    211     }
    212 
    213     @Override
    214     public void setColorFilter(ColorFilter cf) {
    215         if (mPaint == null && cf == null) {
    216             // Fast common case -- leave at no color filter.
    217             return;
    218         }
    219         getPaint().setColorFilter(cf);
    220         invalidateSelf();
    221     }
    222 
    223     @Override
    224     public void setDither(boolean dither) {
    225         if (mPaint == null && dither == DEFAULT_DITHER) {
    226             // Fast common case -- leave at default dither.
    227             return;
    228         }
    229         getPaint().setDither(dither);
    230         invalidateSelf();
    231     }
    232 
    233     @Override
    234     public void setFilterBitmap(boolean filter) {
    235         getPaint().setFilterBitmap(filter);
    236         invalidateSelf();
    237     }
    238 
    239     @Override
    240     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
    241             throws XmlPullParserException, IOException {
    242         super.inflate(r, parser, attrs);
    243 
    244         TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.NinePatchDrawable);
    245 
    246         final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0);
    247         if (id == 0) {
    248             throw new XmlPullParserException(parser.getPositionDescription() +
    249                     ": <nine-patch> requires a valid src attribute");
    250         }
    251 
    252         final boolean dither = a.getBoolean(
    253                 com.android.internal.R.styleable.NinePatchDrawable_dither,
    254                 DEFAULT_DITHER);
    255         final BitmapFactory.Options options = new BitmapFactory.Options();
    256         if (dither) {
    257             options.inDither = false;
    258         }
    259         options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
    260 
    261         final Rect padding = new Rect();
    262         Bitmap bitmap = null;
    263 
    264         try {
    265             final TypedValue value = new TypedValue();
    266             final InputStream is = r.openRawResource(id, value);
    267 
    268             bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
    269 
    270             is.close();
    271         } catch (IOException e) {
    272             // Ignore
    273         }
    274 
    275         if (bitmap == null) {
    276             throw new XmlPullParserException(parser.getPositionDescription() +
    277                     ": <nine-patch> requires a valid src attribute");
    278         } else if (bitmap.getNinePatchChunk() == null) {
    279             throw new XmlPullParserException(parser.getPositionDescription() +
    280                     ": <nine-patch> requires a valid 9-patch source image");
    281         }
    282 
    283         setNinePatchState(new NinePatchState(
    284                 new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"),
    285                 padding, dither), r);
    286         mNinePatchState.mTargetDensity = mTargetDensity;
    287 
    288         a.recycle();
    289     }
    290 
    291     public Paint getPaint() {
    292         if (mPaint == null) {
    293             mPaint = new Paint();
    294             mPaint.setDither(DEFAULT_DITHER);
    295         }
    296         return mPaint;
    297     }
    298 
    299     /**
    300      * Retrieves the width of the source .png file (before resizing).
    301      */
    302     @Override
    303     public int getIntrinsicWidth() {
    304         return mBitmapWidth;
    305     }
    306 
    307     /**
    308      * Retrieves the height of the source .png file (before resizing).
    309      */
    310     @Override
    311     public int getIntrinsicHeight() {
    312         return mBitmapHeight;
    313     }
    314 
    315     @Override
    316     public int getMinimumWidth() {
    317         return mBitmapWidth;
    318     }
    319 
    320     @Override
    321     public int getMinimumHeight() {
    322         return mBitmapHeight;
    323     }
    324 
    325     /**
    326      * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
    327      * value of OPAQUE or TRANSLUCENT.
    328      */
    329     @Override
    330     public int getOpacity() {
    331         return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
    332                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    333     }
    334 
    335     @Override
    336     public Region getTransparentRegion() {
    337         return mNinePatch.getTransparentRegion(getBounds());
    338     }
    339 
    340     @Override
    341     public ConstantState getConstantState() {
    342         mNinePatchState.mChangingConfigurations = getChangingConfigurations();
    343         return mNinePatchState;
    344     }
    345 
    346     @Override
    347     public Drawable mutate() {
    348         if (!mMutated && super.mutate() == this) {
    349             mNinePatchState = new NinePatchState(mNinePatchState);
    350             mNinePatch = mNinePatchState.mNinePatch;
    351             mMutated = true;
    352         }
    353         return this;
    354     }
    355 
    356     final static class NinePatchState extends ConstantState {
    357         final NinePatch mNinePatch;
    358         final Rect mPadding;
    359         final boolean mDither;
    360         int mChangingConfigurations;
    361         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
    362 
    363         NinePatchState(NinePatch ninePatch, Rect padding) {
    364             this(ninePatch, padding, DEFAULT_DITHER);
    365         }
    366 
    367         NinePatchState(NinePatch ninePatch, Rect rect, boolean dither) {
    368             mNinePatch = ninePatch;
    369             mPadding = rect;
    370             mDither = dither;
    371         }
    372 
    373         NinePatchState(NinePatchState state) {
    374             mNinePatch = new NinePatch(state.mNinePatch);
    375             // Note we don't copy the padding because it is immutable.
    376             mPadding = state.mPadding;
    377             mDither = state.mDither;
    378             mChangingConfigurations = state.mChangingConfigurations;
    379             mTargetDensity = state.mTargetDensity;
    380         }
    381 
    382         @Override
    383         public Drawable newDrawable() {
    384             return new NinePatchDrawable(this, null);
    385         }
    386 
    387         @Override
    388         public Drawable newDrawable(Resources res) {
    389             return new NinePatchDrawable(this, res);
    390         }
    391 
    392         @Override
    393         public int getChangingConfigurations() {
    394             return mChangingConfigurations;
    395         }
    396     }
    397 
    398     private NinePatchDrawable(NinePatchState state, Resources res) {
    399         setNinePatchState(state, res);
    400     }
    401 }
    402