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