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.os.Build; 38 import android.util.AttributeSet; 39 import android.view.ViewDebug; 40 41 import com.android.internal.R; 42 43 import org.xmlpull.v1.XmlPullParser; 44 import org.xmlpull.v1.XmlPullParserException; 45 46 import java.io.IOException; 47 48 /** 49 * A specialized Drawable that fills the Canvas with a specified color. 50 * Note that a ColorDrawable ignores the ColorFilter. 51 * 52 * <p>It can be defined in an XML file with the <code><color></code> element.</p> 53 * 54 * @attr ref android.R.styleable#ColorDrawable_color 55 */ 56 public class ColorDrawable extends Drawable { 57 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 58 private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 59 60 @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") 61 private ColorState mColorState; 62 private BlendModeColorFilter mBlendModeColorFilter; 63 64 private boolean mMutated; 65 66 /** 67 * Creates a new black ColorDrawable. 68 */ ColorDrawable()69 public ColorDrawable() { 70 mColorState = new ColorState(); 71 } 72 73 /** 74 * Creates a new ColorDrawable with the specified color. 75 * 76 * @param color The color to draw. 77 */ ColorDrawable(@olorInt int color)78 public ColorDrawable(@ColorInt int color) { 79 mColorState = new ColorState(); 80 81 setColor(color); 82 } 83 84 @Override getChangingConfigurations()85 public @Config int getChangingConfigurations() { 86 return super.getChangingConfigurations() | mColorState.getChangingConfigurations(); 87 } 88 89 /** 90 * A mutable BitmapDrawable still shares its Bitmap with any other Drawable 91 * that comes from the same resource. 92 * 93 * @return This drawable. 94 */ 95 @Override mutate()96 public Drawable mutate() { 97 if (!mMutated && super.mutate() == this) { 98 mColorState = new ColorState(mColorState); 99 mMutated = true; 100 } 101 return this; 102 } 103 104 /** 105 * @hide 106 */ clearMutated()107 public void clearMutated() { 108 super.clearMutated(); 109 mMutated = false; 110 } 111 112 @Override draw(Canvas canvas)113 public void draw(Canvas canvas) { 114 final ColorFilter colorFilter = mPaint.getColorFilter(); 115 if ((mColorState.mUseColor >>> 24) != 0 || colorFilter != null 116 || mBlendModeColorFilter != null) { 117 if (colorFilter == null) { 118 mPaint.setColorFilter(mBlendModeColorFilter); 119 } 120 121 mPaint.setColor(mColorState.mUseColor); 122 canvas.drawRect(getBounds(), mPaint); 123 124 // Restore original color filter. 125 mPaint.setColorFilter(colorFilter); 126 } 127 } 128 129 /** 130 * Gets the drawable's color value. 131 * 132 * @return int The color to draw. 133 */ 134 @ColorInt getColor()135 public int getColor() { 136 return mColorState.mUseColor; 137 } 138 139 /** 140 * Sets the drawable's color value. This action will clobber the results of 141 * prior calls to {@link #setAlpha(int)} on this object, which side-affected 142 * the underlying color. 143 * 144 * @param color The color to draw. 145 */ setColor(@olorInt int color)146 public void setColor(@ColorInt int color) { 147 if (mColorState.mBaseColor != color || mColorState.mUseColor != color) { 148 mColorState.mBaseColor = mColorState.mUseColor = color; 149 invalidateSelf(); 150 } 151 } 152 153 /** 154 * Returns the alpha value of this drawable's color. Note this may not be the same alpha value 155 * provided in {@link Drawable#setAlpha(int)}. Instead this will return the alpha of the color 156 * combined with the alpha provided by setAlpha 157 * 158 * @return A value between 0 and 255. 159 * 160 * @see ColorDrawable#setAlpha(int) 161 */ 162 @Override getAlpha()163 public int getAlpha() { 164 return mColorState.mUseColor >>> 24; 165 } 166 167 /** 168 * Applies the given alpha to the underlying color. Note if the color already has 169 * an alpha applied to it, this will apply this alpha to the existing value instead of 170 * overwriting it. 171 * 172 * @param alpha The alpha value to set, between 0 and 255. 173 */ 174 @Override setAlpha(int alpha)175 public void setAlpha(int alpha) { 176 alpha += alpha >> 7; // make it 0..256 177 final int baseAlpha = mColorState.mBaseColor >>> 24; 178 final int useAlpha = baseAlpha * alpha >> 8; 179 final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24); 180 if (mColorState.mUseColor != useColor) { 181 mColorState.mUseColor = useColor; 182 invalidateSelf(); 183 } 184 } 185 186 /** 187 * Sets the color filter applied to this color. 188 * <p> 189 * Only supported on version {@link android.os.Build.VERSION_CODES#LOLLIPOP} and 190 * above. Calling this method has no effect on earlier versions. 191 * 192 * @see android.graphics.drawable.Drawable#setColorFilter(ColorFilter) 193 */ 194 @Override setColorFilter(ColorFilter colorFilter)195 public void setColorFilter(ColorFilter colorFilter) { 196 mPaint.setColorFilter(colorFilter); 197 } 198 199 /** 200 * Returns the color filter applied to this color configured by 201 * {@link #setColorFilter(ColorFilter)} 202 * 203 * @see android.graphics.drawable.Drawable#getColorFilter() 204 */ 205 @Override getColorFilter()206 public @Nullable ColorFilter getColorFilter() { 207 return mPaint.getColorFilter(); 208 } 209 210 @Override setTintList(ColorStateList tint)211 public void setTintList(ColorStateList tint) { 212 mColorState.mTint = tint; 213 mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint, 214 mColorState.mBlendMode); 215 invalidateSelf(); 216 } 217 218 @Override setTintBlendMode(@onNull BlendMode blendMode)219 public void setTintBlendMode(@NonNull BlendMode blendMode) { 220 mColorState.mBlendMode = blendMode; 221 mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mColorState.mTint, 222 blendMode); 223 invalidateSelf(); 224 } 225 226 @Override onStateChange(int[] stateSet)227 protected boolean onStateChange(int[] stateSet) { 228 final ColorState state = mColorState; 229 if (state.mTint != null && state.mBlendMode != null) { 230 mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint, 231 state.mBlendMode); 232 return true; 233 } 234 return false; 235 } 236 237 @Override isStateful()238 public boolean isStateful() { 239 return mColorState.mTint != null && mColorState.mTint.isStateful(); 240 } 241 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