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