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.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.content.res.ColorStateList;
     22 import android.content.res.Resources;
     23 import android.content.res.Resources.Theme;
     24 import android.content.res.TypedArray;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.Canvas;
     28 import android.graphics.ColorFilter;
     29 import android.graphics.Insets;
     30 import android.graphics.NinePatch;
     31 import android.graphics.Outline;
     32 import android.graphics.Paint;
     33 import android.graphics.PixelFormat;
     34 import android.graphics.PorterDuff;
     35 import android.graphics.PorterDuff.Mode;
     36 import android.graphics.PorterDuffColorFilter;
     37 import android.graphics.Rect;
     38 import android.graphics.Region;
     39 import android.util.AttributeSet;
     40 import android.util.DisplayMetrics;
     41 import android.util.LayoutDirection;
     42 import android.util.TypedValue;
     43 
     44 import com.android.internal.R;
     45 
     46 import org.xmlpull.v1.XmlPullParser;
     47 import org.xmlpull.v1.XmlPullParserException;
     48 
     49 import java.io.IOException;
     50 import java.io.InputStream;
     51 
     52 /**
     53  *
     54  * A resizeable bitmap, with stretchable areas that you define. This type of image
     55  * is defined in a .png file with a special format.
     56  *
     57  * <div class="special reference">
     58  * <h3>Developer Guides</h3>
     59  * <p>For more information about how to use a NinePatchDrawable, read the
     60  * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
     61  * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
     62  * file using the draw9patch tool, see the
     63  * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
     64  */
     65 public class NinePatchDrawable extends Drawable {
     66     // dithering helps a lot, and is pretty cheap, so default is true
     67     private static final boolean DEFAULT_DITHER = false;
     68     private NinePatchState mNinePatchState;
     69     private NinePatch mNinePatch;
     70     private PorterDuffColorFilter mTintFilter;
     71     private Rect mPadding;
     72     private Insets mOpticalInsets = Insets.NONE;
     73     private Paint mPaint;
     74     private boolean mMutated;
     75 
     76     private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
     77 
     78     // These are scaled to match the target density.
     79     private int mBitmapWidth = -1;
     80     private int mBitmapHeight = -1;
     81 
     82     NinePatchDrawable() {
     83         mNinePatchState = new NinePatchState();
     84     }
     85 
     86     /**
     87      * Create drawable from raw nine-patch data, not dealing with density.
     88      * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
     89      * to ensure that the drawable has correctly set its target density.
     90      */
     91     @Deprecated
     92     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
     93         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null, null);
     94     }
     95 
     96     /**
     97      * Create drawable from raw nine-patch data, setting initial target density
     98      * based on the display metrics of the resources.
     99      */
    100     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
    101             Rect padding, String srcName) {
    102         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res, null);
    103         mNinePatchState.mTargetDensity = mTargetDensity;
    104     }
    105 
    106     /**
    107      * Create drawable from raw nine-patch data, setting initial target density
    108      * based on the display metrics of the resources.
    109      *
    110      * @hide
    111      */
    112     public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
    113             Rect padding, Rect opticalInsets, String srcName) {
    114         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets),
    115                 res, null);
    116         mNinePatchState.mTargetDensity = mTargetDensity;
    117     }
    118 
    119     /**
    120      * Create drawable from existing nine-patch, not dealing with density.
    121      * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
    122      * to ensure that the drawable has correctly set its target density.
    123      */
    124     @Deprecated
    125     public NinePatchDrawable(NinePatch patch) {
    126         this(new NinePatchState(patch, new Rect()), null, null);
    127     }
    128 
    129     /**
    130      * Create drawable from existing nine-patch, setting initial target density
    131      * based on the display metrics of the resources.
    132      */
    133     public NinePatchDrawable(Resources res, NinePatch patch) {
    134         this(new NinePatchState(patch, new Rect()), res, null);
    135         mNinePatchState.mTargetDensity = mTargetDensity;
    136     }
    137 
    138     /**
    139      * Set the density scale at which this drawable will be rendered. This
    140      * method assumes the drawable will be rendered at the same density as the
    141      * specified canvas.
    142      *
    143      * @param canvas The Canvas from which the density scale must be obtained.
    144      *
    145      * @see android.graphics.Bitmap#setDensity(int)
    146      * @see android.graphics.Bitmap#getDensity()
    147      */
    148     public void setTargetDensity(Canvas canvas) {
    149         setTargetDensity(canvas.getDensity());
    150     }
    151 
    152     /**
    153      * Set the density scale at which this drawable will be rendered.
    154      *
    155      * @param metrics The DisplayMetrics indicating the density scale for this drawable.
    156      *
    157      * @see android.graphics.Bitmap#setDensity(int)
    158      * @see android.graphics.Bitmap#getDensity()
    159      */
    160     public void setTargetDensity(DisplayMetrics metrics) {
    161         setTargetDensity(metrics.densityDpi);
    162     }
    163 
    164     /**
    165      * Set the density at which this drawable will be rendered.
    166      *
    167      * @param density The density scale for this drawable.
    168      *
    169      * @see android.graphics.Bitmap#setDensity(int)
    170      * @see android.graphics.Bitmap#getDensity()
    171      */
    172     public void setTargetDensity(int density) {
    173         if (density != mTargetDensity) {
    174             mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
    175             if (mNinePatch != null) {
    176                 computeBitmapSize();
    177             }
    178             invalidateSelf();
    179         }
    180     }
    181 
    182     private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
    183         int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity);
    184         int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity);
    185         int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity);
    186         int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity);
    187         return Insets.of(left, top, right, bottom);
    188     }
    189 
    190     private void computeBitmapSize() {
    191         final int sdensity = mNinePatch.getDensity();
    192         final int tdensity = mTargetDensity;
    193         if (sdensity == tdensity) {
    194             mBitmapWidth = mNinePatch.getWidth();
    195             mBitmapHeight = mNinePatch.getHeight();
    196             mOpticalInsets = mNinePatchState.mOpticalInsets;
    197         } else {
    198             mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity);
    199             mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), sdensity, tdensity);
    200             if (mNinePatchState.mPadding != null && mPadding != null) {
    201                 Rect dest = mPadding;
    202                 Rect src = mNinePatchState.mPadding;
    203                 if (dest == src) {
    204                     mPadding = dest = new Rect(src);
    205                 }
    206                 dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
    207                 dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
    208                 dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
    209                 dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
    210             }
    211             mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity);
    212         }
    213     }
    214 
    215     private void setNinePatch(NinePatch ninePatch) {
    216         if (mNinePatch != ninePatch) {
    217             mNinePatch = ninePatch;
    218             if (ninePatch != null) {
    219                 computeBitmapSize();
    220             } else {
    221                 mBitmapWidth = mBitmapHeight = -1;
    222                 mOpticalInsets = Insets.NONE;
    223             }
    224             invalidateSelf();
    225         }
    226     }
    227 
    228     @Override
    229     public void draw(Canvas canvas) {
    230         final Rect bounds = getBounds();
    231 
    232         final boolean clearColorFilter;
    233         if (mTintFilter != null && getPaint().getColorFilter() == null) {
    234             mPaint.setColorFilter(mTintFilter);
    235             clearColorFilter = true;
    236         } else {
    237             clearColorFilter = false;
    238         }
    239 
    240         final boolean needsMirroring = needsMirroring();
    241         if (needsMirroring) {
    242             // Mirror the 9patch
    243             canvas.translate(bounds.right - bounds.left, 0);
    244             canvas.scale(-1.0f, 1.0f);
    245         }
    246 
    247         final int restoreAlpha;
    248         if (mNinePatchState.mBaseAlpha != 1.0f) {
    249             restoreAlpha = mPaint.getAlpha();
    250             mPaint.setAlpha((int) (restoreAlpha * mNinePatchState.mBaseAlpha + 0.5f));
    251         } else {
    252             restoreAlpha = -1;
    253         }
    254 
    255         mNinePatch.draw(canvas, bounds, mPaint);
    256 
    257         if (clearColorFilter) {
    258             mPaint.setColorFilter(null);
    259         }
    260 
    261         if (restoreAlpha >= 0) {
    262             mPaint.setAlpha(restoreAlpha);
    263         }
    264     }
    265 
    266     @Override
    267     public int getChangingConfigurations() {
    268         return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
    269     }
    270 
    271     @Override
    272     public boolean getPadding(Rect padding) {
    273         final Rect scaledPadding = mPadding;
    274         if (scaledPadding != null) {
    275             if (needsMirroring()) {
    276                 padding.set(scaledPadding.right, scaledPadding.top,
    277                         scaledPadding.left, scaledPadding.bottom);
    278             } else {
    279                 padding.set(scaledPadding);
    280             }
    281             return (padding.left | padding.top | padding.right | padding.bottom) != 0;
    282         }
    283         return false;
    284     }
    285 
    286     @Override
    287     public void getOutline(@NonNull Outline outline) {
    288         final Rect bounds = getBounds();
    289         if (bounds.isEmpty()) return;
    290 
    291         if (mNinePatchState != null) {
    292             NinePatch.InsetStruct insets = mNinePatchState.getBitmap().getNinePatchInsets();
    293             if (insets != null) {
    294                 final Rect outlineInsets = insets.outlineRect;
    295                 outline.setRoundRect(bounds.left + outlineInsets.left,
    296                         bounds.top + outlineInsets.top,
    297                         bounds.right - outlineInsets.right,
    298                         bounds.bottom - outlineInsets.bottom,
    299                         insets.outlineRadius);
    300                 outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f));
    301                 return;
    302             }
    303         }
    304         super.getOutline(outline);
    305     }
    306 
    307     /**
    308      * @hide
    309      */
    310     @Override
    311     public Insets getOpticalInsets() {
    312         if (needsMirroring()) {
    313             return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
    314                     mOpticalInsets.left, mOpticalInsets.bottom);
    315         } else {
    316             return mOpticalInsets;
    317         }
    318     }
    319 
    320     @Override
    321     public void setAlpha(int alpha) {
    322         if (mPaint == null && alpha == 0xFF) {
    323             // Fast common case -- leave at normal alpha.
    324             return;
    325         }
    326         getPaint().setAlpha(alpha);
    327         invalidateSelf();
    328     }
    329 
    330     @Override
    331     public int getAlpha() {
    332         if (mPaint == null) {
    333             // Fast common case -- normal alpha.
    334             return 0xFF;
    335         }
    336         return getPaint().getAlpha();
    337     }
    338 
    339     @Override
    340     public void setColorFilter(ColorFilter cf) {
    341         if (mPaint == null && cf == null) {
    342             // Fast common case -- leave at no color filter.
    343             return;
    344         }
    345         getPaint().setColorFilter(cf);
    346         invalidateSelf();
    347     }
    348 
    349     @Override
    350     public void setTintList(ColorStateList tint) {
    351         mNinePatchState.mTint = tint;
    352         mTintFilter = updateTintFilter(mTintFilter, tint, mNinePatchState.mTintMode);
    353         invalidateSelf();
    354     }
    355 
    356     @Override
    357     public void setTintMode(PorterDuff.Mode tintMode) {
    358         mNinePatchState.mTintMode = tintMode;
    359         mTintFilter = updateTintFilter(mTintFilter, mNinePatchState.mTint, tintMode);
    360         invalidateSelf();
    361     }
    362 
    363     @Override
    364     public void setDither(boolean dither) {
    365         //noinspection PointlessBooleanExpression
    366         if (mPaint == null && dither == DEFAULT_DITHER) {
    367             // Fast common case -- leave at default dither.
    368             return;
    369         }
    370 
    371         getPaint().setDither(dither);
    372         invalidateSelf();
    373     }
    374 
    375     @Override
    376     public void setAutoMirrored(boolean mirrored) {
    377         mNinePatchState.mAutoMirrored = mirrored;
    378     }
    379 
    380     private boolean needsMirroring() {
    381         return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
    382     }
    383 
    384     @Override
    385     public boolean isAutoMirrored() {
    386         return mNinePatchState.mAutoMirrored;
    387     }
    388 
    389     @Override
    390     public void setFilterBitmap(boolean filter) {
    391         getPaint().setFilterBitmap(filter);
    392         invalidateSelf();
    393     }
    394 
    395     @Override
    396     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
    397             throws XmlPullParserException, IOException {
    398         super.inflate(r, parser, attrs, theme);
    399 
    400         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.NinePatchDrawable);
    401         updateStateFromTypedArray(a);
    402         a.recycle();
    403     }
    404 
    405     /**
    406      * Updates the constant state from the values in the typed array.
    407      */
    408     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
    409         final Resources r = a.getResources();
    410         final NinePatchState state = mNinePatchState;
    411 
    412         // Account for any configuration changes.
    413         state.mChangingConfigurations |= a.getChangingConfigurations();
    414 
    415         // Extract the theme attributes, if any.
    416         state.mThemeAttrs = a.extractThemeAttrs();
    417 
    418         state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
    419 
    420         final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
    421         if (srcResId != 0) {
    422             final BitmapFactory.Options options = new BitmapFactory.Options();
    423             options.inDither = !state.mDither;
    424             options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
    425 
    426             final Rect padding = new Rect();
    427             final Rect opticalInsets = new Rect();
    428             Bitmap bitmap = null;
    429 
    430             try {
    431                 final TypedValue value = new TypedValue();
    432                 final InputStream is = r.openRawResource(srcResId, value);
    433 
    434                 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
    435 
    436                 is.close();
    437             } catch (IOException e) {
    438                 // Ignore
    439             }
    440 
    441             if (bitmap == null) {
    442                 throw new XmlPullParserException(a.getPositionDescription() +
    443                         ": <nine-patch> requires a valid src attribute");
    444             } else if (bitmap.getNinePatchChunk() == null) {
    445                 throw new XmlPullParserException(a.getPositionDescription() +
    446                         ": <nine-patch> requires a valid 9-patch source image");
    447             }
    448 
    449             bitmap.getOpticalInsets(opticalInsets);
    450 
    451             state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
    452             state.mPadding = padding;
    453             state.mOpticalInsets = Insets.of(opticalInsets);
    454         }
    455 
    456         state.mAutoMirrored = a.getBoolean(
    457                 R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
    458         state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
    459 
    460         final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
    461         if (tintMode != -1) {
    462             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
    463         }
    464 
    465         final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
    466         if (tint != null) {
    467             state.mTint = tint;
    468         }
    469 
    470         // Update local properties.
    471         initializeWithState(state, r);
    472 
    473         // Push density applied by setNinePatchState into state.
    474         state.mTargetDensity = mTargetDensity;
    475     }
    476 
    477     @Override
    478     public void applyTheme(Theme t) {
    479         super.applyTheme(t);
    480 
    481         final NinePatchState state = mNinePatchState;
    482         if (state == null || state.mThemeAttrs == null) {
    483             return;
    484         }
    485 
    486         final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.NinePatchDrawable);
    487         try {
    488             updateStateFromTypedArray(a);
    489         } catch (XmlPullParserException e) {
    490             throw new RuntimeException(e);
    491         } finally {
    492             a.recycle();
    493         }
    494     }
    495 
    496     @Override
    497     public boolean canApplyTheme() {
    498         return mNinePatchState != null && mNinePatchState.mThemeAttrs != null;
    499     }
    500 
    501     public Paint getPaint() {
    502         if (mPaint == null) {
    503             mPaint = new Paint();
    504             mPaint.setDither(DEFAULT_DITHER);
    505         }
    506         return mPaint;
    507     }
    508 
    509     /**
    510      * Retrieves the width of the source .png file (before resizing).
    511      */
    512     @Override
    513     public int getIntrinsicWidth() {
    514         return mBitmapWidth;
    515     }
    516 
    517     /**
    518      * Retrieves the height of the source .png file (before resizing).
    519      */
    520     @Override
    521     public int getIntrinsicHeight() {
    522         return mBitmapHeight;
    523     }
    524 
    525     @Override
    526     public int getMinimumWidth() {
    527         return mBitmapWidth;
    528     }
    529 
    530     @Override
    531     public int getMinimumHeight() {
    532         return mBitmapHeight;
    533     }
    534 
    535     /**
    536      * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
    537      * value of OPAQUE or TRANSLUCENT.
    538      */
    539     @Override
    540     public int getOpacity() {
    541         return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
    542                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
    543     }
    544 
    545     @Override
    546     public Region getTransparentRegion() {
    547         return mNinePatch.getTransparentRegion(getBounds());
    548     }
    549 
    550     @Override
    551     public ConstantState getConstantState() {
    552         mNinePatchState.mChangingConfigurations = getChangingConfigurations();
    553         return mNinePatchState;
    554     }
    555 
    556     @Override
    557     public Drawable mutate() {
    558         if (!mMutated && super.mutate() == this) {
    559             mNinePatchState = new NinePatchState(mNinePatchState);
    560             mNinePatch = mNinePatchState.mNinePatch;
    561             mMutated = true;
    562         }
    563         return this;
    564     }
    565 
    566     @Override
    567     protected boolean onStateChange(int[] stateSet) {
    568         final NinePatchState state = mNinePatchState;
    569         if (state.mTint != null && state.mTintMode != null) {
    570             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    571             return true;
    572         }
    573 
    574         return false;
    575     }
    576 
    577     @Override
    578     public boolean isStateful() {
    579         final NinePatchState s = mNinePatchState;
    580         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
    581     }
    582 
    583     final static class NinePatchState extends ConstantState {
    584         // Values loaded during inflation.
    585         int[] mThemeAttrs = null;
    586         NinePatch mNinePatch = null;
    587         ColorStateList mTint = null;
    588         Mode mTintMode = DEFAULT_TINT_MODE;
    589         Rect mPadding = null;
    590         Insets mOpticalInsets = Insets.NONE;
    591         float mBaseAlpha = 1.0f;
    592         boolean mDither = DEFAULT_DITHER;
    593         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
    594         boolean mAutoMirrored = false;
    595 
    596         int mChangingConfigurations;
    597 
    598         NinePatchState() {
    599             // Empty constructor.
    600         }
    601 
    602         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) {
    603             this(ninePatch, padding, null, DEFAULT_DITHER, false);
    604         }
    605 
    606         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
    607                 @Nullable Rect opticalInsets) {
    608             this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
    609         }
    610 
    611         NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding,
    612                 @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) {
    613             mNinePatch = ninePatch;
    614             mPadding = padding;
    615             mOpticalInsets = Insets.of(opticalInsets);
    616             mDither = dither;
    617             mAutoMirrored = autoMirror;
    618         }
    619 
    620         // Copy constructor
    621 
    622         NinePatchState(@NonNull NinePatchState state) {
    623             // We don't deep-copy any fields because they are all immutable.
    624             mNinePatch = state.mNinePatch;
    625             mTint = state.mTint;
    626             mTintMode = state.mTintMode;
    627             mThemeAttrs = state.mThemeAttrs;
    628             mPadding = state.mPadding;
    629             mOpticalInsets = state.mOpticalInsets;
    630             mBaseAlpha = state.mBaseAlpha;
    631             mDither = state.mDither;
    632             mChangingConfigurations = state.mChangingConfigurations;
    633             mTargetDensity = state.mTargetDensity;
    634             mAutoMirrored = state.mAutoMirrored;
    635         }
    636 
    637         @Override
    638         public boolean canApplyTheme() {
    639             return mThemeAttrs != null;
    640         }
    641 
    642         @Override
    643         public Bitmap getBitmap() {
    644             return mNinePatch.getBitmap();
    645         }
    646 
    647         @Override
    648         public Drawable newDrawable() {
    649             return new NinePatchDrawable(this, null, null);
    650         }
    651 
    652         @Override
    653         public Drawable newDrawable(Resources res) {
    654             return new NinePatchDrawable(this, res, null);
    655         }
    656 
    657         @Override
    658         public Drawable newDrawable(Resources res, Theme theme) {
    659             return new NinePatchDrawable(this, res, theme);
    660         }
    661 
    662         @Override
    663         public int getChangingConfigurations() {
    664             return mChangingConfigurations;
    665         }
    666     }
    667 
    668     /**
    669      * The one constructor to rule them all. This is called by all public
    670      * constructors to set the state and initialize local properties.
    671      */
    672     private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) {
    673         if (theme != null && state.canApplyTheme()) {
    674             // If we need to apply a theme, implicitly mutate.
    675             mNinePatchState = new NinePatchState(state);
    676             applyTheme(theme);
    677         } else {
    678             mNinePatchState = state;
    679         }
    680 
    681         initializeWithState(state, res);
    682     }
    683 
    684     /**
    685      * Initializes local dynamic properties from state.
    686      */
    687     private void initializeWithState(NinePatchState state, Resources res) {
    688         if (res != null) {
    689             mTargetDensity = res.getDisplayMetrics().densityDpi;
    690         } else {
    691             mTargetDensity = state.mTargetDensity;
    692         }
    693 
    694         // If we can, avoid calling any methods that initialize Paint.
    695         if (state.mDither != DEFAULT_DITHER) {
    696             setDither(state.mDither);
    697         }
    698 
    699         // Make a local copy of the padding.
    700         if (state.mPadding != null) {
    701             mPadding = new Rect(state.mPadding);
    702         }
    703 
    704         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    705         setNinePatch(state.mNinePatch);
    706     }
    707 }
    708