Home | History | Annotate | Download | only in res
      1 /*
      2  * Copyright (C) 2007 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.content.res;
     18 
     19 import android.annotation.ColorInt;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.content.pm.ActivityInfo.Config;
     23 import android.content.res.Resources.Theme;
     24 import android.graphics.Color;
     25 
     26 import com.android.internal.R;
     27 import com.android.internal.util.ArrayUtils;
     28 import com.android.internal.util.GrowingArrayUtils;
     29 
     30 import org.xmlpull.v1.XmlPullParser;
     31 import org.xmlpull.v1.XmlPullParserException;
     32 
     33 import android.util.AttributeSet;
     34 import android.util.Log;
     35 import android.util.MathUtils;
     36 import android.util.SparseArray;
     37 import android.util.StateSet;
     38 import android.util.Xml;
     39 import android.os.Parcel;
     40 import android.os.Parcelable;
     41 
     42 import java.io.IOException;
     43 import java.lang.ref.WeakReference;
     44 import java.util.Arrays;
     45 
     46 /**
     47  *
     48  * Lets you map {@link android.view.View} state sets to colors.
     49  * <p>
     50  * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
     51  * "color" subdirectory directory of an application's resource directory. The XML file contains
     52  * a single "selector" element with a number of "item" elements inside. For example:
     53  * <pre>
     54  * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
     55  *   &lt;item android:state_focused="true"
     56  *           android:color="@color/sample_focused" /&gt;
     57  *   &lt;item android:state_pressed="true"
     58  *           android:state_enabled="false"
     59  *           android:color="@color/sample_disabled_pressed" /&gt;
     60  *   &lt;item android:state_enabled="false"
     61  *           android:color="@color/sample_disabled_not_pressed" /&gt;
     62  *   &lt;item android:color="@color/sample_default" /&gt;
     63  * &lt;/selector&gt;
     64  * </pre>
     65  *
     66  * This defines a set of state spec / color pairs where each state spec specifies a set of
     67  * states that a view must either be in or not be in and the color specifies the color associated
     68  * with that spec.
     69  *
     70  * <a name="StateSpec"></a>
     71  * <h3>State specs</h3>
     72  * <p>
     73  * Each item defines a set of state spec and color pairs, where the state spec is a series of
     74  * attributes set to either {@code true} or {@code false} to represent inclusion or exclusion. If
     75  * an attribute is not specified for an item, it may be any value.
     76  * <p>
     77  * For example, the following item will be matched whenever the focused state is set; any other
     78  * states may be set or unset:
     79  * <pre>
     80  * &lt;item android:state_focused="true"
     81  *         android:color="@color/sample_focused" /&gt;
     82  * </pre>
     83  * <p>
     84  * Typically, a color state list will reference framework-defined state attributes such as
     85  * {@link android.R.attr#state_focused android:state_focused} or
     86  * {@link android.R.attr#state_enabled android:state_enabled}; however, app-defined attributes may
     87  * also be used.
     88  * <p>
     89  * <strong>Note:</strong> The list of state specs will be matched against in the order that they
     90  * appear in the XML file. For this reason, more-specific items should be placed earlier in the
     91  * file. An item with no state spec is considered to match any set of states and is generally
     92  * useful as a final item to be used as a default.
     93  * <p>
     94  * If an item with no state spec if placed before other items, those items
     95  * will be ignored.
     96  *
     97  * <a name="ItemAttributes"></a>
     98  * <h3>Item attributes</h3>
     99  * <p>
    100  * Each item must define an {@link android.R.attr#color android:color} attribute, which may be
    101  * an HTML-style hex color, a reference to a color resource, or -- in API 23 and above -- a theme
    102  * attribute that resolves to a color.
    103  * <p>
    104  * Starting with API 23, items may optionally define an {@link android.R.attr#alpha android:alpha}
    105  * attribute to modify the base color's opacity. This attribute takes a either floating-point value
    106  * between 0 and 1 or a theme attribute that resolves as such. The item's overall color is
    107  * calculated by multiplying by the base color's alpha channel by the {@code alpha} value. For
    108  * example, the following item represents the theme's accent color at 50% opacity:
    109  * <pre>
    110  * &lt;item android:state_enabled="false"
    111  *         android:color="?android:attr/colorAccent"
    112  *         android:alpha="0.5" /&gt;
    113  * </pre>
    114  *
    115  * <a name="DeveloperGuide"></a>
    116  * <h3>Developer guide</h3>
    117  * <p>
    118  * For more information, see the guide to
    119  * <a href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State
    120  * List Resource</a>.
    121  *
    122  * @attr ref android.R.styleable#ColorStateListItem_alpha
    123  * @attr ref android.R.styleable#ColorStateListItem_color
    124  */
    125 public class ColorStateList extends ComplexColor implements Parcelable {
    126     private static final String TAG = "ColorStateList";
    127 
    128     private static final int DEFAULT_COLOR = Color.RED;
    129     private static final int[][] EMPTY = new int[][] { new int[0] };
    130 
    131     /** Thread-safe cache of single-color ColorStateLists. */
    132     private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>();
    133 
    134     /** Lazily-created factory for this color state list. */
    135     private ColorStateListFactory mFactory;
    136 
    137     private int[][] mThemeAttrs;
    138     private @Config int mChangingConfigurations;
    139 
    140     private int[][] mStateSpecs;
    141     private int[] mColors;
    142     private int mDefaultColor;
    143     private boolean mIsOpaque;
    144 
    145     private ColorStateList() {
    146         // Not publicly instantiable.
    147     }
    148 
    149     /**
    150      * Creates a ColorStateList that returns the specified mapping from
    151      * states to colors.
    152      */
    153     public ColorStateList(int[][] states, @ColorInt int[] colors) {
    154         mStateSpecs = states;
    155         mColors = colors;
    156 
    157         onColorsChanged();
    158     }
    159 
    160     /**
    161      * @return A ColorStateList containing a single color.
    162      */
    163     @NonNull
    164     public static ColorStateList valueOf(@ColorInt int color) {
    165         synchronized (sCache) {
    166             final int index = sCache.indexOfKey(color);
    167             if (index >= 0) {
    168                 final ColorStateList cached = sCache.valueAt(index).get();
    169                 if (cached != null) {
    170                     return cached;
    171                 }
    172 
    173                 // Prune missing entry.
    174                 sCache.removeAt(index);
    175             }
    176 
    177             // Prune the cache before adding new items.
    178             final int N = sCache.size();
    179             for (int i = N - 1; i >= 0; i--) {
    180                 if (sCache.valueAt(i).get() == null) {
    181                     sCache.removeAt(i);
    182                 }
    183             }
    184 
    185             final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
    186             sCache.put(color, new WeakReference<>(csl));
    187             return csl;
    188         }
    189     }
    190 
    191     /**
    192      * Creates a ColorStateList with the same properties as another
    193      * ColorStateList.
    194      * <p>
    195      * The properties of the new ColorStateList can be modified without
    196      * affecting the source ColorStateList.
    197      *
    198      * @param orig the source color state list
    199      */
    200     private ColorStateList(ColorStateList orig) {
    201         if (orig != null) {
    202             mChangingConfigurations = orig.mChangingConfigurations;
    203             mStateSpecs = orig.mStateSpecs;
    204             mDefaultColor = orig.mDefaultColor;
    205             mIsOpaque = orig.mIsOpaque;
    206 
    207             // Deep copy, these may change due to applyTheme().
    208             mThemeAttrs = orig.mThemeAttrs.clone();
    209             mColors = orig.mColors.clone();
    210         }
    211     }
    212 
    213     /**
    214      * Creates a ColorStateList from an XML document.
    215      *
    216      * @param r Resources against which the ColorStateList should be inflated.
    217      * @param parser Parser for the XML document defining the ColorStateList.
    218      * @return A new color state list.
    219      *
    220      * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
    221      */
    222     @NonNull
    223     @Deprecated
    224     public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
    225             throws XmlPullParserException, IOException {
    226         return createFromXml(r, parser, null);
    227     }
    228 
    229     /**
    230      * Creates a ColorStateList from an XML document using given a set of
    231      * {@link Resources} and a {@link Theme}.
    232      *
    233      * @param r Resources against which the ColorStateList should be inflated.
    234      * @param parser Parser for the XML document defining the ColorStateList.
    235      * @param theme Optional theme to apply to the color state list, may be
    236      *              {@code null}.
    237      * @return A new color state list.
    238      */
    239     @NonNull
    240     public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
    241             @Nullable Theme theme) throws XmlPullParserException, IOException {
    242         final AttributeSet attrs = Xml.asAttributeSet(parser);
    243 
    244         int type;
    245         while ((type = parser.next()) != XmlPullParser.START_TAG
    246                    && type != XmlPullParser.END_DOCUMENT) {
    247             // Seek parser to start tag.
    248         }
    249 
    250         if (type != XmlPullParser.START_TAG) {
    251             throw new XmlPullParserException("No start tag found");
    252         }
    253 
    254         return createFromXmlInner(r, parser, attrs, theme);
    255     }
    256 
    257     /**
    258      * Create from inside an XML document. Called on a parser positioned at a
    259      * tag in an XML document, tries to create a ColorStateList from that tag.
    260      *
    261      * @throws XmlPullParserException if the current tag is not &lt;selector>
    262      * @return A new color state list for the current tag.
    263      */
    264     @NonNull
    265     static ColorStateList createFromXmlInner(@NonNull Resources r,
    266             @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
    267             throws XmlPullParserException, IOException {
    268         final String name = parser.getName();
    269         if (!name.equals("selector")) {
    270             throw new XmlPullParserException(
    271                     parser.getPositionDescription() + ": invalid color state list tag " + name);
    272         }
    273 
    274         final ColorStateList colorStateList = new ColorStateList();
    275         colorStateList.inflate(r, parser, attrs, theme);
    276         return colorStateList;
    277     }
    278 
    279     /**
    280      * Creates a new ColorStateList that has the same states and colors as this
    281      * one but where each color has the specified alpha value (0-255).
    282      *
    283      * @param alpha The new alpha channel value (0-255).
    284      * @return A new color state list.
    285      */
    286     @NonNull
    287     public ColorStateList withAlpha(int alpha) {
    288         final int[] colors = new int[mColors.length];
    289         final int len = colors.length;
    290         for (int i = 0; i < len; i++) {
    291             colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
    292         }
    293 
    294         return new ColorStateList(mStateSpecs, colors);
    295     }
    296 
    297     /**
    298      * Fill in this object based on the contents of an XML "selector" element.
    299      */
    300     private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
    301             @NonNull AttributeSet attrs, @Nullable Theme theme)
    302             throws XmlPullParserException, IOException {
    303         final int innerDepth = parser.getDepth()+1;
    304         int depth;
    305         int type;
    306 
    307         @Config int changingConfigurations = 0;
    308         int defaultColor = DEFAULT_COLOR;
    309 
    310         boolean hasUnresolvedAttrs = false;
    311 
    312         int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
    313         int[][] themeAttrsList = new int[stateSpecList.length][];
    314         int[] colorList = new int[stateSpecList.length];
    315         int listSize = 0;
    316 
    317         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
    318                && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
    319             if (type != XmlPullParser.START_TAG || depth > innerDepth
    320                     || !parser.getName().equals("item")) {
    321                 continue;
    322             }
    323 
    324             final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
    325                     R.styleable.ColorStateListItem);
    326             final int[] themeAttrs = a.extractThemeAttrs();
    327             final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA);
    328             final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
    329 
    330             changingConfigurations |= a.getChangingConfigurations();
    331 
    332             a.recycle();
    333 
    334             // Parse all unrecognized attributes as state specifiers.
    335             int j = 0;
    336             final int numAttrs = attrs.getAttributeCount();
    337             int[] stateSpec = new int[numAttrs];
    338             for (int i = 0; i < numAttrs; i++) {
    339                 final int stateResId = attrs.getAttributeNameResource(i);
    340                 switch (stateResId) {
    341                     case R.attr.color:
    342                     case R.attr.alpha:
    343                         // Recognized attribute, ignore.
    344                         break;
    345                     default:
    346                         stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
    347                                 ? stateResId : -stateResId;
    348                 }
    349             }
    350             stateSpec = StateSet.trimStateSet(stateSpec, j);
    351 
    352             // Apply alpha modulation. If we couldn't resolve the color or
    353             // alpha yet, the default values leave us enough information to
    354             // modulate again during applyTheme().
    355             final int color = modulateColorAlpha(baseColor, alphaMod);
    356             if (listSize == 0 || stateSpec.length == 0) {
    357                 defaultColor = color;
    358             }
    359 
    360             if (themeAttrs != null) {
    361                 hasUnresolvedAttrs = true;
    362             }
    363 
    364             colorList = GrowingArrayUtils.append(colorList, listSize, color);
    365             themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
    366             stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
    367             listSize++;
    368         }
    369 
    370         mChangingConfigurations = changingConfigurations;
    371         mDefaultColor = defaultColor;
    372 
    373         if (hasUnresolvedAttrs) {
    374             mThemeAttrs = new int[listSize][];
    375             System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
    376         } else {
    377             mThemeAttrs = null;
    378         }
    379 
    380         mColors = new int[listSize];
    381         mStateSpecs = new int[listSize][];
    382         System.arraycopy(colorList, 0, mColors, 0, listSize);
    383         System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
    384 
    385         onColorsChanged();
    386     }
    387 
    388     /**
    389      * Returns whether a theme can be applied to this color state list, which
    390      * usually indicates that the color state list has unresolved theme
    391      * attributes.
    392      *
    393      * @return whether a theme can be applied to this color state list
    394      * @hide only for resource preloading
    395      */
    396     @Override
    397     public boolean canApplyTheme() {
    398         return mThemeAttrs != null;
    399     }
    400 
    401     /**
    402      * Applies a theme to this color state list.
    403      * <p>
    404      * <strong>Note:</strong> Applying a theme may affect the changing
    405      * configuration parameters of this color state list. After calling this
    406      * method, any dependent configurations must be updated by obtaining the
    407      * new configuration mask from {@link #getChangingConfigurations()}.
    408      *
    409      * @param t the theme to apply
    410      */
    411     private void applyTheme(Theme t) {
    412         if (mThemeAttrs == null) {
    413             return;
    414         }
    415 
    416         boolean hasUnresolvedAttrs = false;
    417 
    418         final int[][] themeAttrsList = mThemeAttrs;
    419         final int N = themeAttrsList.length;
    420         for (int i = 0; i < N; i++) {
    421             if (themeAttrsList[i] != null) {
    422                 final TypedArray a = t.resolveAttributes(themeAttrsList[i],
    423                         R.styleable.ColorStateListItem);
    424 
    425                 final float defaultAlphaMod;
    426                 if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) {
    427                     // If the base color hasn't been resolved yet, the current
    428                     // color's alpha channel is either full-opacity (if we
    429                     // haven't resolved the alpha modulation yet) or
    430                     // pre-modulated. Either is okay as a default value.
    431                     defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f;
    432                 } else {
    433                     // Otherwise, the only correct default value is 1. Even if
    434                     // nothing is resolved during this call, we can apply this
    435                     // multiple times without losing of information.
    436                     defaultAlphaMod = 1.0f;
    437                 }
    438 
    439                 // Extract the theme attributes, if any, before attempting to
    440                 // read from the typed array. This prevents a crash if we have
    441                 // unresolved attrs.
    442                 themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
    443                 if (themeAttrsList[i] != null) {
    444                     hasUnresolvedAttrs = true;
    445                 }
    446 
    447                 final int baseColor = a.getColor(
    448                         R.styleable.ColorStateListItem_color, mColors[i]);
    449                 final float alphaMod = a.getFloat(
    450                         R.styleable.ColorStateListItem_alpha, defaultAlphaMod);
    451                 mColors[i] = modulateColorAlpha(baseColor, alphaMod);
    452 
    453                 // Account for any configuration changes.
    454                 mChangingConfigurations |= a.getChangingConfigurations();
    455 
    456                 a.recycle();
    457             }
    458         }
    459 
    460         if (!hasUnresolvedAttrs) {
    461             mThemeAttrs = null;
    462         }
    463 
    464         onColorsChanged();
    465     }
    466 
    467     /**
    468      * Returns an appropriately themed color state list.
    469      *
    470      * @param t the theme to apply
    471      * @return a copy of the color state list with the theme applied, or the
    472      *         color state list itself if there were no unresolved theme
    473      *         attributes
    474      * @hide only for resource preloading
    475      */
    476     @Override
    477     public ColorStateList obtainForTheme(Theme t) {
    478         if (t == null || !canApplyTheme()) {
    479             return this;
    480         }
    481 
    482         final ColorStateList clone = new ColorStateList(this);
    483         clone.applyTheme(t);
    484         return clone;
    485     }
    486 
    487     /**
    488      * Returns a mask of the configuration parameters for which this color
    489      * state list may change, requiring that it be re-created.
    490      *
    491      * @return a mask of the changing configuration parameters, as defined by
    492      *         {@link android.content.pm.ActivityInfo}
    493      *
    494      * @see android.content.pm.ActivityInfo
    495      */
    496     public @Config int getChangingConfigurations() {
    497         return super.getChangingConfigurations() | mChangingConfigurations;
    498     }
    499 
    500     private int modulateColorAlpha(int baseColor, float alphaMod) {
    501         if (alphaMod == 1.0f) {
    502             return baseColor;
    503         }
    504 
    505         final int baseAlpha = Color.alpha(baseColor);
    506         final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
    507         return (baseColor & 0xFFFFFF) | (alpha << 24);
    508     }
    509 
    510     /**
    511      * Indicates whether this color state list contains more than one state spec
    512      * and will change color based on state.
    513      *
    514      * @return True if this color state list changes color based on state, false
    515      *         otherwise.
    516      * @see #getColorForState(int[], int)
    517      */
    518     @Override
    519     public boolean isStateful() {
    520         return mStateSpecs.length > 1;
    521     }
    522 
    523     /**
    524      * Indicates whether this color state list is opaque, which means that every
    525      * color returned from {@link #getColorForState(int[], int)} has an alpha
    526      * value of 255.
    527      *
    528      * @return True if this color state list is opaque.
    529      */
    530     public boolean isOpaque() {
    531         return mIsOpaque;
    532     }
    533 
    534     /**
    535      * Return the color associated with the given set of
    536      * {@link android.view.View} states.
    537      *
    538      * @param stateSet an array of {@link android.view.View} states
    539      * @param defaultColor the color to return if there's no matching state
    540      *                     spec in this {@link ColorStateList} that matches the
    541      *                     stateSet.
    542      *
    543      * @return the color associated with that set of states in this {@link ColorStateList}.
    544      */
    545     public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
    546         final int setLength = mStateSpecs.length;
    547         for (int i = 0; i < setLength; i++) {
    548             final int[] stateSpec = mStateSpecs[i];
    549             if (StateSet.stateSetMatches(stateSpec, stateSet)) {
    550                 return mColors[i];
    551             }
    552         }
    553         return defaultColor;
    554     }
    555 
    556     /**
    557      * Return the default color in this {@link ColorStateList}.
    558      *
    559      * @return the default color in this {@link ColorStateList}.
    560      */
    561     @ColorInt
    562     public int getDefaultColor() {
    563         return mDefaultColor;
    564     }
    565 
    566     /**
    567      * Return the states in this {@link ColorStateList}. The returned array
    568      * should not be modified.
    569      *
    570      * @return the states in this {@link ColorStateList}
    571      * @hide
    572      */
    573     public int[][] getStates() {
    574         return mStateSpecs;
    575     }
    576 
    577     /**
    578      * Return the colors in this {@link ColorStateList}. The returned array
    579      * should not be modified.
    580      *
    581      * @return the colors in this {@link ColorStateList}
    582      * @hide
    583      */
    584     public int[] getColors() {
    585         return mColors;
    586     }
    587 
    588     /**
    589      * Returns whether the specified state is referenced in any of the state
    590      * specs contained within this ColorStateList.
    591      * <p>
    592      * Any reference, either positive or negative {ex. ~R.attr.state_enabled},
    593      * will cause this method to return {@code true}. Wildcards are not counted
    594      * as references.
    595      *
    596      * @param state the state to search for
    597      * @return {@code true} if the state if referenced, {@code false} otherwise
    598      * @hide Use only as directed. For internal use only.
    599      */
    600     public boolean hasState(int state) {
    601         final int[][] stateSpecs = mStateSpecs;
    602         final int specCount = stateSpecs.length;
    603         for (int specIndex = 0; specIndex < specCount; specIndex++) {
    604             final int[] states = stateSpecs[specIndex];
    605             final int stateCount = states.length;
    606             for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) {
    607                 if (states[stateIndex] == state || states[stateIndex] == ~state) {
    608                     return true;
    609                 }
    610             }
    611         }
    612         return false;
    613     }
    614 
    615     @Override
    616     public String toString() {
    617         return "ColorStateList{" +
    618                "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
    619                "mChangingConfigurations=" + mChangingConfigurations +
    620                "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
    621                "mColors=" + Arrays.toString(mColors) +
    622                "mDefaultColor=" + mDefaultColor + '}';
    623     }
    624 
    625     /**
    626      * Updates the default color and opacity.
    627      */
    628     private void onColorsChanged() {
    629         int defaultColor = DEFAULT_COLOR;
    630         boolean isOpaque = true;
    631 
    632         final int[][] states = mStateSpecs;
    633         final int[] colors = mColors;
    634         final int N = states.length;
    635         if (N > 0) {
    636             defaultColor = colors[0];
    637 
    638             for (int i = N - 1; i > 0; i--) {
    639                 if (states[i].length == 0) {
    640                     defaultColor = colors[i];
    641                     break;
    642                 }
    643             }
    644 
    645             for (int i = 0; i < N; i++) {
    646                 if (Color.alpha(colors[i]) != 0xFF) {
    647                     isOpaque = false;
    648                     break;
    649                 }
    650             }
    651         }
    652 
    653         mDefaultColor = defaultColor;
    654         mIsOpaque = isOpaque;
    655     }
    656 
    657     /**
    658      * @return a factory that can create new instances of this ColorStateList
    659      * @hide only for resource preloading
    660      */
    661     public ConstantState<ComplexColor> getConstantState() {
    662         if (mFactory == null) {
    663             mFactory = new ColorStateListFactory(this);
    664         }
    665         return mFactory;
    666     }
    667 
    668     private static class ColorStateListFactory extends ConstantState<ComplexColor> {
    669         private final ColorStateList mSrc;
    670 
    671         public ColorStateListFactory(ColorStateList src) {
    672             mSrc = src;
    673         }
    674 
    675         @Override
    676         public @Config int getChangingConfigurations() {
    677             return mSrc.mChangingConfigurations;
    678         }
    679 
    680         @Override
    681         public ColorStateList newInstance() {
    682             return mSrc;
    683         }
    684 
    685         @Override
    686         public ColorStateList newInstance(Resources res, Theme theme) {
    687             return (ColorStateList) mSrc.obtainForTheme(theme);
    688         }
    689     }
    690 
    691     @Override
    692     public int describeContents() {
    693         return 0;
    694     }
    695 
    696     @Override
    697     public void writeToParcel(Parcel dest, int flags) {
    698         if (canApplyTheme()) {
    699             Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
    700         }
    701         final int N = mStateSpecs.length;
    702         dest.writeInt(N);
    703         for (int i = 0; i < N; i++) {
    704             dest.writeIntArray(mStateSpecs[i]);
    705         }
    706         dest.writeIntArray(mColors);
    707     }
    708 
    709     public static final Parcelable.Creator<ColorStateList> CREATOR =
    710             new Parcelable.Creator<ColorStateList>() {
    711         @Override
    712         public ColorStateList[] newArray(int size) {
    713             return new ColorStateList[size];
    714         }
    715 
    716         @Override
    717         public ColorStateList createFromParcel(Parcel source) {
    718             final int N = source.readInt();
    719             final int[][] stateSpecs = new int[N][];
    720             for (int i = 0; i < N; i++) {
    721                 stateSpecs[i] = source.createIntArray();
    722             }
    723             final int[] colors = source.createIntArray();
    724             return new ColorStateList(stateSpecs, colors);
    725         }
    726     };
    727 }
    728