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