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