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