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