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