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