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 
17 package android.graphics.drawable;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.TestApi;
23 import android.content.pm.ActivityInfo.Config;
24 import android.graphics.*;
25 import android.graphics.PorterDuff.Mode;
26 import android.content.res.ColorStateList;
27 import android.content.res.Resources;
28 import android.content.res.Resources.Theme;
29 import android.content.res.TypedArray;
30 import android.util.AttributeSet;
31 import android.view.ViewDebug;
32 
33 import com.android.internal.R;
34 
35 import org.xmlpull.v1.XmlPullParser;
36 import org.xmlpull.v1.XmlPullParserException;
37 
38 import 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  */
48 public 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      */
ColorDrawable()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      */
ColorDrawable(@olorInt int color)69     public ColorDrawable(@ColorInt int color) {
70         mColorState = new ColorState();
71 
72         setColor(color);
73     }
74 
75     @Override
getChangingConfigurations()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
mutate()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      */
clearMutated()98     public void clearMutated() {
99         super.clearMutated();
100         mMutated = false;
101     }
102 
103     @Override
draw(Canvas canvas)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
getColor()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      */
setColor(@olorInt int color)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
getAlpha()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
setAlpha(int alpha)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
setColorFilter(ColorFilter colorFilter)179     public void setColorFilter(ColorFilter colorFilter) {
180         mPaint.setColorFilter(colorFilter);
181     }
182 
183     @Override
setTintList(ColorStateList tint)184     public void setTintList(ColorStateList tint) {
185         mColorState.mTint = tint;
186         mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode);
187         invalidateSelf();
188     }
189 
190     @Override
setTintMode(Mode tintMode)191     public void setTintMode(Mode tintMode) {
192         mColorState.mTintMode = tintMode;
193         mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode);
194         invalidateSelf();
195     }
196 
197     @Override
onStateChange(int[] stateSet)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
isStateful()208     public boolean isStateful() {
209         return mColorState.mTint != null && mColorState.mTint.isStateful();
210     }
211 
212     /** @hide */
213     @Override
hasFocusStateSpecified()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
setXfermode(@ullable Xfermode mode)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
getXfermode()233     public Xfermode getXfermode() {
234         return mPaint.getXfermode();
235     }
236 
237     @Override
getOpacity()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
getOutline(@onNull Outline outline)253     public void getOutline(@NonNull Outline outline) {
254         outline.setRect(getBounds());
255         outline.setAlpha(getAlpha() / 255.0f);
256     }
257 
258     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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      */
updateStateFromTypedArray(TypedArray a)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
canApplyTheme()287     public boolean canApplyTheme() {
288         return mColorState.canApplyTheme() || super.canApplyTheme();
289     }
290 
291     @Override
applyTheme(Theme t)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
getConstantState()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 
ColorState()327         ColorState() {
328             // Empty constructor.
329         }
330 
ColorState(ColorState state)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
canApplyTheme()341         public boolean canApplyTheme() {
342             return mThemeAttrs != null
343                     || (mTint != null && mTint.canApplyTheme());
344         }
345 
346         @Override
newDrawable()347         public Drawable newDrawable() {
348             return new ColorDrawable(this, null);
349         }
350 
351         @Override
newDrawable(Resources res)352         public Drawable newDrawable(Resources res) {
353             return new ColorDrawable(this, res);
354         }
355 
356         @Override
getChangingConfigurations()357         public @Config int getChangingConfigurations() {
358             return mChangingConfigurations
359                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
360         }
361     }
362 
ColorDrawable(ColorState state, Resources res)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      */
updateLocalState(Resources r)374     private void updateLocalState(Resources r) {
375         mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode);
376     }
377 }
378