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