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