1 /*
2  * Copyright 2018 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 package android.graphics.drawable;
17 
18 import android.annotation.IntRange;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.pm.ActivityInfo;
22 import android.content.res.ColorStateList;
23 import android.content.res.Resources;
24 import android.graphics.BlendMode;
25 import android.graphics.Canvas;
26 import android.graphics.ColorFilter;
27 import android.graphics.PixelFormat;
28 import android.graphics.Rect;
29 import android.util.MathUtils;
30 
31 /**
32  * A Drawable that manages a {@link ColorDrawable} to make it stateful and backed by a
33  * {@link ColorStateList}.
34  */
35 public class ColorStateListDrawable extends Drawable implements Drawable.Callback {
36     private ColorDrawable mColorDrawable;
37     private ColorStateListDrawableState mState;
38     private boolean mMutated = false;
39 
ColorStateListDrawable()40     public ColorStateListDrawable() {
41         mState = new ColorStateListDrawableState();
42         initializeColorDrawable();
43     }
44 
ColorStateListDrawable(@onNull ColorStateList colorStateList)45     public ColorStateListDrawable(@NonNull ColorStateList colorStateList) {
46         mState = new ColorStateListDrawableState();
47         initializeColorDrawable();
48         setColorStateList(colorStateList);
49     }
50 
51     @Override
draw(@onNull Canvas canvas)52     public void draw(@NonNull Canvas canvas) {
53         mColorDrawable.draw(canvas);
54     }
55 
56     @Override
57     @IntRange(from = 0, to = 255)
getAlpha()58     public int getAlpha() {
59         return mColorDrawable.getAlpha();
60     }
61 
62     @Override
isStateful()63     public boolean isStateful() {
64         return mState.isStateful();
65     }
66 
67     @Override
hasFocusStateSpecified()68     public boolean hasFocusStateSpecified() {
69         return mState.hasFocusStateSpecified();
70     }
71 
72     @Override
getCurrent()73     public @NonNull Drawable getCurrent() {
74         return mColorDrawable;
75     }
76 
77     @Override
applyTheme(@onNull Resources.Theme t)78     public void applyTheme(@NonNull Resources.Theme t) {
79         super.applyTheme(t);
80 
81         if (mState.mColor != null) {
82             setColorStateList(mState.mColor.obtainForTheme(t));
83         }
84 
85         if (mState.mTint != null) {
86             setTintList(mState.mTint.obtainForTheme(t));
87         }
88     }
89 
90     @Override
canApplyTheme()91     public boolean canApplyTheme() {
92         return super.canApplyTheme() || mState.canApplyTheme();
93     }
94 
95     @Override
setAlpha(@ntRangefrom = 0, to = 255) int alpha)96     public void setAlpha(@IntRange(from = 0, to = 255) int alpha) {
97         mState.mAlpha = alpha;
98         onStateChange(getState());
99     }
100 
101     /**
102      * Remove the alpha override, reverting to the alpha defined on each color in the
103      * {@link ColorStateList}.
104      */
clearAlpha()105     public void clearAlpha() {
106         mState.mAlpha = -1;
107         onStateChange(getState());
108     }
109 
110     @Override
setTintList(@ullable ColorStateList tint)111     public void setTintList(@Nullable ColorStateList tint) {
112         mState.mTint = tint;
113         mColorDrawable.setTintList(tint);
114         onStateChange(getState());
115     }
116 
117     @Override
setTintBlendMode(@onNull BlendMode blendMode)118     public void setTintBlendMode(@NonNull BlendMode blendMode) {
119         mState.mBlendMode = blendMode;
120         mColorDrawable.setTintBlendMode(blendMode);
121         onStateChange(getState());
122     }
123 
124     @Override
getColorFilter()125     public @Nullable ColorFilter getColorFilter() {
126         return mColorDrawable.getColorFilter();
127     }
128 
129     @Override
setColorFilter(@ullable ColorFilter colorFilter)130     public void setColorFilter(@Nullable ColorFilter colorFilter) {
131         mColorDrawable.setColorFilter(colorFilter);
132     }
133 
134     @Override
getOpacity()135     public @PixelFormat.Opacity int getOpacity() {
136         return mColorDrawable.getOpacity();
137     }
138 
139     @Override
onBoundsChange(Rect bounds)140     protected void onBoundsChange(Rect bounds) {
141         super.onBoundsChange(bounds);
142         mColorDrawable.setBounds(bounds);
143     }
144 
145     @Override
onStateChange(int[] state)146     protected boolean onStateChange(int[] state) {
147         if (mState.mColor != null) {
148             int color = mState.mColor.getColorForState(state, mState.mColor.getDefaultColor());
149 
150             if (mState.mAlpha != -1) {
151                 color = (color & 0xFFFFFF) | MathUtils.constrain(mState.mAlpha, 0, 255) << 24;
152             }
153 
154             if (color != mColorDrawable.getColor()) {
155                 mColorDrawable.setColor(color);
156                 mColorDrawable.setState(state);
157                 return true;
158             } else {
159                 return mColorDrawable.setState(state);
160             }
161         } else {
162             return false;
163         }
164     }
165 
166     @Override
invalidateDrawable(@onNull Drawable who)167     public void invalidateDrawable(@NonNull Drawable who) {
168         if (who == mColorDrawable && getCallback() != null) {
169             getCallback().invalidateDrawable(this);
170         }
171     }
172 
173     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)174     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
175         if (who == mColorDrawable && getCallback() != null) {
176             getCallback().scheduleDrawable(this, what, when);
177         }
178     }
179 
180     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)181     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
182         if (who == mColorDrawable && getCallback() != null) {
183             getCallback().unscheduleDrawable(this, what);
184         }
185     }
186 
187     @Override
getConstantState()188     public @NonNull ConstantState getConstantState() {
189         mState.mChangingConfigurations = mState.mChangingConfigurations
190                 | (getChangingConfigurations() & ~mState.getChangingConfigurations());
191         return mState;
192     }
193 
194     /**
195      * Returns the ColorStateList backing this Drawable, or a new ColorStateList of the default
196      * ColorDrawable color if one hasn't been defined yet.
197      *
198      * @return a ColorStateList
199      */
getColorStateList()200     public @NonNull ColorStateList getColorStateList() {
201         if (mState.mColor == null) {
202             return ColorStateList.valueOf(mColorDrawable.getColor());
203         } else {
204             return mState.mColor;
205         }
206     }
207 
208     @Override
getChangingConfigurations()209     public int getChangingConfigurations() {
210         return super.getChangingConfigurations() | mState.getChangingConfigurations();
211     }
212 
213     @Override
mutate()214     public @NonNull Drawable mutate() {
215         if (!mMutated && super.mutate() == this) {
216             mState = new ColorStateListDrawableState(mState);
217             mMutated = true;
218         }
219         return this;
220     }
221 
222     /**
223      * @hide
224      */
225     @Override
clearMutated()226     public void clearMutated() {
227         super.clearMutated();
228         mMutated = false;
229     }
230 
231     /**
232      * Replace this Drawable's ColorStateList. It is not copied, so changes will propagate on the
233      * next call to {@link #setState(int[])}.
234      *
235      * @param colorStateList A color state list to attach.
236      */
setColorStateList(@onNull ColorStateList colorStateList)237     public void setColorStateList(@NonNull ColorStateList colorStateList) {
238         mState.mColor = colorStateList;
239         onStateChange(getState());
240     }
241 
242     static final class ColorStateListDrawableState extends ConstantState {
243         ColorStateList mColor = null;
244         ColorStateList mTint = null;
245         int mAlpha = -1;
246         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
247         @ActivityInfo.Config int mChangingConfigurations = 0;
248 
ColorStateListDrawableState()249         ColorStateListDrawableState() {
250         }
251 
ColorStateListDrawableState(ColorStateListDrawableState state)252         ColorStateListDrawableState(ColorStateListDrawableState state) {
253             mColor = state.mColor;
254             mTint = state.mTint;
255             mAlpha = state.mAlpha;
256             mBlendMode = state.mBlendMode;
257             mChangingConfigurations = state.mChangingConfigurations;
258         }
259 
260         @Override
newDrawable()261         public Drawable newDrawable() {
262             return new ColorStateListDrawable(this);
263         }
264 
265         @Override
getChangingConfigurations()266         public @ActivityInfo.Config int getChangingConfigurations() {
267             return mChangingConfigurations
268                     | (mColor != null ? mColor.getChangingConfigurations() : 0)
269                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
270         }
271 
isStateful()272         public boolean isStateful() {
273             return (mColor != null && mColor.isStateful())
274                     || (mTint != null && mTint.isStateful());
275         }
276 
hasFocusStateSpecified()277         public boolean hasFocusStateSpecified() {
278             return (mColor != null && mColor.hasFocusStateSpecified())
279                     || (mTint != null && mTint.hasFocusStateSpecified());
280         }
281 
282         @Override
canApplyTheme()283         public boolean canApplyTheme() {
284             return (mColor != null && mColor.canApplyTheme())
285                     || (mTint != null && mTint.canApplyTheme());
286         }
287     }
288 
ColorStateListDrawable(@onNull ColorStateListDrawableState state)289     private ColorStateListDrawable(@NonNull ColorStateListDrawableState state) {
290         mState = state;
291         initializeColorDrawable();
292     }
293 
initializeColorDrawable()294     private void initializeColorDrawable() {
295         mColorDrawable = new ColorDrawable();
296         mColorDrawable.setCallback(this);
297 
298         if (mState.mTint != null) {
299             mColorDrawable.setTintList(mState.mTint);
300         }
301 
302         if (mState.mBlendMode != DEFAULT_BLEND_MODE) {
303             mColorDrawable.setTintBlendMode(mState.mBlendMode);
304         }
305     }
306 }
307