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