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