ColorDrawable.java revision b27b47df6b9e77259badd0eaa9a3978bacd830b8
1/*
2 * Copyright (C) 2008 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.ColorInt;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.annotation.TestApi;
23import android.content.pm.ActivityInfo.Config;
24import android.graphics.*;
25import android.graphics.PorterDuff.Mode;
26import android.content.res.ColorStateList;
27import android.content.res.Resources;
28import android.content.res.Resources.Theme;
29import android.content.res.TypedArray;
30import android.util.AttributeSet;
31import android.view.ViewDebug;
32
33import com.android.internal.R;
34
35import org.xmlpull.v1.XmlPullParser;
36import org.xmlpull.v1.XmlPullParserException;
37
38import java.io.IOException;
39
40/**
41 * A specialized Drawable that fills the Canvas with a specified color.
42 * Note that a ColorDrawable ignores the ColorFilter.
43 *
44 * <p>It can be defined in an XML file with the <code>&lt;color></code> element.</p>
45 *
46 * @attr ref android.R.styleable#ColorDrawable_color
47 */
48public class ColorDrawable extends Drawable {
49    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
50
51    @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_")
52    private ColorState mColorState;
53    private PorterDuffColorFilter mTintFilter;
54
55    private boolean mMutated;
56
57    /**
58     * Creates a new black ColorDrawable.
59     */
60    public ColorDrawable() {
61        mColorState = new ColorState();
62    }
63
64    /**
65     * Creates a new ColorDrawable with the specified color.
66     *
67     * @param color The color to draw.
68     */
69    public ColorDrawable(@ColorInt int color) {
70        mColorState = new ColorState();
71
72        setColor(color);
73    }
74
75    @Override
76    public @Config int getChangingConfigurations() {
77        return super.getChangingConfigurations() | mColorState.getChangingConfigurations();
78    }
79
80    /**
81     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
82     * that comes from the same resource.
83     *
84     * @return This drawable.
85     */
86    @Override
87    public Drawable mutate() {
88        if (!mMutated && super.mutate() == this) {
89            mColorState = new ColorState(mColorState);
90            mMutated = true;
91        }
92        return this;
93    }
94
95    /**
96     * @hide
97     */
98    public void clearMutated() {
99        super.clearMutated();
100        mMutated = false;
101    }
102
103    @Override
104    public void draw(Canvas canvas) {
105        final ColorFilter colorFilter = mPaint.getColorFilter();
106        if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) {
107            if (colorFilter == null) {
108                mPaint.setColorFilter(mTintFilter);
109            }
110
111            mPaint.setColor(mColorState.mUseColor);
112            canvas.drawRect(getBounds(), mPaint);
113
114            // Restore original color filter.
115            mPaint.setColorFilter(colorFilter);
116        }
117    }
118
119    /**
120     * Gets the drawable's color value.
121     *
122     * @return int The color to draw.
123     */
124    @ColorInt
125    public int getColor() {
126        return mColorState.mUseColor;
127    }
128
129    /**
130     * Sets the drawable's color value. This action will clobber the results of
131     * prior calls to {@link #setAlpha(int)} on this object, which side-affected
132     * the underlying color.
133     *
134     * @param color The color to draw.
135     */
136    public void setColor(@ColorInt int color) {
137        if (mColorState.mBaseColor != color || mColorState.mUseColor != color) {
138            mColorState.mBaseColor = mColorState.mUseColor = color;
139            invalidateSelf();
140        }
141    }
142
143    /**
144     * Returns the alpha value of this drawable's color.
145     *
146     * @return A value between 0 and 255.
147     */
148    @Override
149    public int getAlpha() {
150        return mColorState.mUseColor >>> 24;
151    }
152
153    /**
154     * Sets the color's alpha value.
155     *
156     * @param alpha The alpha value to set, between 0 and 255.
157     */
158    @Override
159    public void setAlpha(int alpha) {
160        alpha += alpha >> 7;   // make it 0..256
161        final int baseAlpha = mColorState.mBaseColor >>> 24;
162        final int useAlpha = baseAlpha * alpha >> 8;
163        final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24);
164        if (mColorState.mUseColor != useColor) {
165            mColorState.mUseColor = useColor;
166            invalidateSelf();
167        }
168    }
169
170    /**
171     * Sets the color filter applied to this color.
172     * <p>
173     * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and
174     * above. Calling this method has no effect on earlier versions.
175     *
176     * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter)
177     */
178    @Override
179    public void setColorFilter(ColorFilter colorFilter) {
180        mPaint.setColorFilter(colorFilter);
181    }
182
183    @Override
184    public void setTintList(ColorStateList tint) {
185        mColorState.mTint = tint;
186        mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode);
187        invalidateSelf();
188    }
189
190    @Override
191    public void setTintMode(Mode tintMode) {
192        mColorState.mTintMode = tintMode;
193        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode);
194        invalidateSelf();
195    }
196
197    @Override
198    protected boolean onStateChange(int[] stateSet) {
199        final ColorState state = mColorState;
200        if (state.mTint != null && state.mTintMode != null) {
201            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
202            return true;
203        }
204        return false;
205    }
206
207    @Override
208    public boolean isStateful() {
209        return mColorState.mTint != null && mColorState.mTint.isStateful();
210    }
211
212    /** @hide */
213    @Override
214    public boolean hasFocusStateSpecified() {
215        return mColorState.mTint != null && mColorState.mTint.hasFocusStateSpecified();
216    }
217
218    /**
219     * @hide
220     * @param mode new transfer mode
221     */
222    @Override
223    public void setXfermode(@Nullable Xfermode mode) {
224        mPaint.setXfermode(mode);
225        invalidateSelf();
226    }
227
228    /**
229     * @hide
230     * @return current transfer mode
231     */
232    @TestApi
233    public Xfermode getXfermode() {
234        return mPaint.getXfermode();
235    }
236
237    @Override
238    public int getOpacity() {
239        if (mTintFilter != null || mPaint.getColorFilter() != null) {
240            return PixelFormat.TRANSLUCENT;
241        }
242
243        switch (mColorState.mUseColor >>> 24) {
244            case 255:
245                return PixelFormat.OPAQUE;
246            case 0:
247                return PixelFormat.TRANSPARENT;
248        }
249        return PixelFormat.TRANSLUCENT;
250    }
251
252    @Override
253    public void getOutline(@NonNull Outline outline) {
254        outline.setRect(getBounds());
255        outline.setAlpha(getAlpha() / 255.0f);
256    }
257
258    @Override
259    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
260            throws XmlPullParserException, IOException {
261        super.inflate(r, parser, attrs, theme);
262
263        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable);
264        updateStateFromTypedArray(a);
265        a.recycle();
266
267        updateLocalState(r);
268    }
269
270    /**
271     * Updates the constant state from the values in the typed array.
272     */
273    private void updateStateFromTypedArray(TypedArray a) {
274        final ColorState state = mColorState;
275
276        // Account for any configuration changes.
277        state.mChangingConfigurations |= a.getChangingConfigurations();
278
279        // Extract the theme attributes, if any.
280        state.mThemeAttrs = a.extractThemeAttrs();
281
282        state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor);
283        state.mUseColor = state.mBaseColor;
284    }
285
286    @Override
287    public boolean canApplyTheme() {
288        return mColorState.canApplyTheme() || super.canApplyTheme();
289    }
290
291    @Override
292    public void applyTheme(Theme t) {
293        super.applyTheme(t);
294
295        final ColorState state = mColorState;
296        if (state == null) {
297            return;
298        }
299
300        if (state.mThemeAttrs != null) {
301            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ColorDrawable);
302            updateStateFromTypedArray(a);
303            a.recycle();
304        }
305
306        if (state.mTint != null && state.mTint.canApplyTheme()) {
307            state.mTint = state.mTint.obtainForTheme(t);
308        }
309
310        updateLocalState(t.getResources());
311    }
312
313    @Override
314    public ConstantState getConstantState() {
315        return mColorState;
316    }
317
318    final static class ColorState extends ConstantState {
319        int[] mThemeAttrs;
320        int mBaseColor; // base color, independent of setAlpha()
321        @ViewDebug.ExportedProperty
322        int mUseColor;  // basecolor modulated by setAlpha()
323        @Config int mChangingConfigurations;
324        ColorStateList mTint = null;
325        Mode mTintMode = DEFAULT_TINT_MODE;
326
327        ColorState() {
328            // Empty constructor.
329        }
330
331        ColorState(ColorState state) {
332            mThemeAttrs = state.mThemeAttrs;
333            mBaseColor = state.mBaseColor;
334            mUseColor = state.mUseColor;
335            mChangingConfigurations = state.mChangingConfigurations;
336            mTint = state.mTint;
337            mTintMode = state.mTintMode;
338        }
339
340        @Override
341        public boolean canApplyTheme() {
342            return mThemeAttrs != null
343                    || (mTint != null && mTint.canApplyTheme());
344        }
345
346        @Override
347        public Drawable newDrawable() {
348            return new ColorDrawable(this, null);
349        }
350
351        @Override
352        public Drawable newDrawable(Resources res) {
353            return new ColorDrawable(this, res);
354        }
355
356        @Override
357        public @Config int getChangingConfigurations() {
358            return mChangingConfigurations
359                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
360        }
361    }
362
363    private ColorDrawable(ColorState state, Resources res) {
364        mColorState = state;
365
366        updateLocalState(res);
367    }
368
369    /**
370     * Initializes local dynamic properties from state. This should be called
371     * after significant state changes, e.g. from the One True Constructor and
372     * after inflating or applying a theme.
373     */
374    private void updateLocalState(Resources r) {
375        mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode);
376    }
377}
378