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 17package android.graphics.drawable; 18 19import android.annotation.ColorInt; 20import android.annotation.NonNull; 21import android.content.pm.ActivityInfo.Config; 22import android.graphics.*; 23import android.graphics.PorterDuff.Mode; 24import android.content.res.ColorStateList; 25import android.content.res.Resources; 26import android.content.res.Resources.Theme; 27import android.content.res.TypedArray; 28import android.util.AttributeSet; 29import android.view.ViewDebug; 30 31import com.android.internal.R; 32 33import org.xmlpull.v1.XmlPullParser; 34import org.xmlpull.v1.XmlPullParserException; 35 36import java.io.IOException; 37 38/** 39 * A specialized Drawable that fills the Canvas with a specified color. 40 * Note that a ColorDrawable ignores the ColorFilter. 41 * 42 * <p>It can be defined in an XML file with the <code><color></code> element.</p> 43 * 44 * @attr ref android.R.styleable#ColorDrawable_color 45 */ 46public class ColorDrawable extends Drawable { 47 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 48 49 @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") 50 private ColorState mColorState; 51 private PorterDuffColorFilter mTintFilter; 52 53 private boolean mMutated; 54 55 /** 56 * Creates a new black ColorDrawable. 57 */ 58 public ColorDrawable() { 59 mColorState = new ColorState(); 60 } 61 62 /** 63 * Creates a new ColorDrawable with the specified color. 64 * 65 * @param color The color to draw. 66 */ 67 public ColorDrawable(@ColorInt int color) { 68 mColorState = new ColorState(); 69 70 setColor(color); 71 } 72 73 @Override 74 public @Config int getChangingConfigurations() { 75 return super.getChangingConfigurations() | mColorState.getChangingConfigurations(); 76 } 77 78 /** 79 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 80 * that comes from the same resource. 81 * 82 * @return This drawable. 83 */ 84 @Override 85 public Drawable mutate() { 86 if (!mMutated && super.mutate() == this) { 87 mColorState = new ColorState(mColorState); 88 mMutated = true; 89 } 90 return this; 91 } 92 93 /** 94 * @hide 95 */ 96 public void clearMutated() { 97 super.clearMutated(); 98 mMutated = false; 99 } 100 101 @Override 102 public void draw(Canvas canvas) { 103 final ColorFilter colorFilter = mPaint.getColorFilter(); 104 if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null || mTintFilter != null) { 105 if (colorFilter == null) { 106 mPaint.setColorFilter(mTintFilter); 107 } 108 109 mPaint.setColor(mColorState.mUseColor); 110 canvas.drawRect(getBounds(), mPaint); 111 112 // Restore original color filter. 113 mPaint.setColorFilter(colorFilter); 114 } 115 } 116 117 /** 118 * Gets the drawable's color value. 119 * 120 * @return int The color to draw. 121 */ 122 @ColorInt 123 public int getColor() { 124 return mColorState.mUseColor; 125 } 126 127 /** 128 * Sets the drawable's color value. This action will clobber the results of 129 * prior calls to {@link #setAlpha(int)} on this object, which side-affected 130 * the underlying color. 131 * 132 * @param color The color to draw. 133 */ 134 public void setColor(@ColorInt int color) { 135 if (mColorState.mBaseColor != color || mColorState.mUseColor != color) { 136 mColorState.mBaseColor = mColorState.mUseColor = color; 137 invalidateSelf(); 138 } 139 } 140 141 /** 142 * Returns the alpha value of this drawable's color. 143 * 144 * @return A value between 0 and 255. 145 */ 146 @Override 147 public int getAlpha() { 148 return mColorState.mUseColor >>> 24; 149 } 150 151 /** 152 * Sets the color's alpha value. 153 * 154 * @param alpha The alpha value to set, between 0 and 255. 155 */ 156 @Override 157 public void setAlpha(int alpha) { 158 alpha += alpha >> 7; // make it 0..256 159 final int baseAlpha = mColorState.mBaseColor >>> 24; 160 final int useAlpha = baseAlpha * alpha >> 8; 161 final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24); 162 if (mColorState.mUseColor != useColor) { 163 mColorState.mUseColor = useColor; 164 invalidateSelf(); 165 } 166 } 167 168 /** 169 * Sets the color filter applied to this color. 170 * <p> 171 * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and 172 * above. Calling this method has no effect on earlier versions. 173 * 174 * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter) 175 */ 176 @Override 177 public void setColorFilter(ColorFilter colorFilter) { 178 mPaint.setColorFilter(colorFilter); 179 } 180 181 @Override 182 public void setTintList(ColorStateList tint) { 183 mColorState.mTint = tint; 184 mTintFilter = updateTintFilter(mTintFilter, tint, mColorState.mTintMode); 185 invalidateSelf(); 186 } 187 188 @Override 189 public void setTintMode(Mode tintMode) { 190 mColorState.mTintMode = tintMode; 191 mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, tintMode); 192 invalidateSelf(); 193 } 194 195 @Override 196 protected boolean onStateChange(int[] stateSet) { 197 final ColorState state = mColorState; 198 if (state.mTint != null && state.mTintMode != null) { 199 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 200 return true; 201 } 202 return false; 203 } 204 205 @Override 206 public boolean isStateful() { 207 return mColorState.mTint != null && mColorState.mTint.isStateful(); 208 } 209 210 @Override 211 public int getOpacity() { 212 if (mTintFilter != null || mPaint.getColorFilter() != null) { 213 return PixelFormat.TRANSLUCENT; 214 } 215 216 switch (mColorState.mUseColor >>> 24) { 217 case 255: 218 return PixelFormat.OPAQUE; 219 case 0: 220 return PixelFormat.TRANSPARENT; 221 } 222 return PixelFormat.TRANSLUCENT; 223 } 224 225 @Override 226 public void getOutline(@NonNull Outline outline) { 227 outline.setRect(getBounds()); 228 outline.setAlpha(getAlpha() / 255.0f); 229 } 230 231 @Override 232 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 233 throws XmlPullParserException, IOException { 234 super.inflate(r, parser, attrs, theme); 235 236 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorDrawable); 237 updateStateFromTypedArray(a); 238 a.recycle(); 239 240 updateLocalState(r); 241 } 242 243 /** 244 * Updates the constant state from the values in the typed array. 245 */ 246 private void updateStateFromTypedArray(TypedArray a) { 247 final ColorState state = mColorState; 248 249 // Account for any configuration changes. 250 state.mChangingConfigurations |= a.getChangingConfigurations(); 251 252 // Extract the theme attributes, if any. 253 state.mThemeAttrs = a.extractThemeAttrs(); 254 255 state.mBaseColor = a.getColor(R.styleable.ColorDrawable_color, state.mBaseColor); 256 state.mUseColor = state.mBaseColor; 257 } 258 259 @Override 260 public boolean canApplyTheme() { 261 return mColorState.canApplyTheme() || super.canApplyTheme(); 262 } 263 264 @Override 265 public void applyTheme(Theme t) { 266 super.applyTheme(t); 267 268 final ColorState state = mColorState; 269 if (state == null) { 270 return; 271 } 272 273 if (state.mThemeAttrs != null) { 274 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ColorDrawable); 275 updateStateFromTypedArray(a); 276 a.recycle(); 277 } 278 279 if (state.mTint != null && state.mTint.canApplyTheme()) { 280 state.mTint = state.mTint.obtainForTheme(t); 281 } 282 283 updateLocalState(t.getResources()); 284 } 285 286 @Override 287 public ConstantState getConstantState() { 288 return mColorState; 289 } 290 291 final static class ColorState extends ConstantState { 292 int[] mThemeAttrs; 293 int mBaseColor; // base color, independent of setAlpha() 294 @ViewDebug.ExportedProperty 295 int mUseColor; // basecolor modulated by setAlpha() 296 @Config int mChangingConfigurations; 297 ColorStateList mTint = null; 298 Mode mTintMode = DEFAULT_TINT_MODE; 299 300 ColorState() { 301 // Empty constructor. 302 } 303 304 ColorState(ColorState state) { 305 mThemeAttrs = state.mThemeAttrs; 306 mBaseColor = state.mBaseColor; 307 mUseColor = state.mUseColor; 308 mChangingConfigurations = state.mChangingConfigurations; 309 mTint = state.mTint; 310 mTintMode = state.mTintMode; 311 } 312 313 @Override 314 public boolean canApplyTheme() { 315 return mThemeAttrs != null 316 || (mTint != null && mTint.canApplyTheme()); 317 } 318 319 @Override 320 public Drawable newDrawable() { 321 return new ColorDrawable(this, null); 322 } 323 324 @Override 325 public Drawable newDrawable(Resources res) { 326 return new ColorDrawable(this, res); 327 } 328 329 @Override 330 public @Config int getChangingConfigurations() { 331 return mChangingConfigurations 332 | (mTint != null ? mTint.getChangingConfigurations() : 0); 333 } 334 } 335 336 private ColorDrawable(ColorState state, Resources res) { 337 mColorState = state; 338 339 updateLocalState(res); 340 } 341 342 /** 343 * Initializes local dynamic properties from state. This should be called 344 * after significant state changes, e.g. from the One True Constructor and 345 * after inflating or applying a theme. 346 */ 347 private void updateLocalState(Resources r) { 348 mTintFilter = updateTintFilter(mTintFilter, mColorState.mTint, mColorState.mTintMode); 349 } 350} 351