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