• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 is 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 
ColorStateList()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      */
ColorStateList(int[][] states, @ColorInt int[] colors)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
valueOf(@olorInt int color)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      */
ColorStateList(ColorStateList orig)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
createFromXml(Resources r, XmlPullParser parser)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
createFromXml(@onNull Resources r, @NonNull XmlPullParser parser, @Nullable Theme theme)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
createFromXmlInner(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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
withAlpha(int alpha)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      */
inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)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
canApplyTheme()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      */
applyTheme(Theme t)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
obtainForTheme(Theme t)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      */
getChangingConfigurations()496     public @Config int getChangingConfigurations() {
497         return super.getChangingConfigurations() | mChangingConfigurations;
498     }
499 
modulateColorAlpha(int baseColor, float alphaMod)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 at least one state spec
512      * and the first spec is not empty (e.g. match-all).
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
isStateful()519     public boolean isStateful() {
520         return mStateSpecs.length >= 1 && mStateSpecs[0].length > 0;
521     }
522 
523     /**
524      * Return whether the state spec list has at least one item explicitly specifying
525      * {@link android.R.attr#state_focused}.
526      * @hide
527      */
hasFocusStateSpecified()528     public boolean hasFocusStateSpecified() {
529         return StateSet.containsAttribute(mStateSpecs, R.attr.state_focused);
530     }
531 
532     /**
533      * Indicates whether this color state list is opaque, which means that every
534      * color returned from {@link #getColorForState(int[], int)} has an alpha
535      * value of 255.
536      *
537      * @return True if this color state list is opaque.
538      */
isOpaque()539     public boolean isOpaque() {
540         return mIsOpaque;
541     }
542 
543     /**
544      * Return the color associated with the given set of
545      * {@link android.view.View} states.
546      *
547      * @param stateSet an array of {@link android.view.View} states
548      * @param defaultColor the color to return if there's no matching state
549      *                     spec in this {@link ColorStateList} that matches the
550      *                     stateSet.
551      *
552      * @return the color associated with that set of states in this {@link ColorStateList}.
553      */
getColorForState(@ullable int[] stateSet, int defaultColor)554     public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
555         final int setLength = mStateSpecs.length;
556         for (int i = 0; i < setLength; i++) {
557             final int[] stateSpec = mStateSpecs[i];
558             if (StateSet.stateSetMatches(stateSpec, stateSet)) {
559                 return mColors[i];
560             }
561         }
562         return defaultColor;
563     }
564 
565     /**
566      * Return the default color in this {@link ColorStateList}.
567      *
568      * @return the default color in this {@link ColorStateList}.
569      */
570     @ColorInt
getDefaultColor()571     public int getDefaultColor() {
572         return mDefaultColor;
573     }
574 
575     /**
576      * Return the states in this {@link ColorStateList}. The returned array
577      * should not be modified.
578      *
579      * @return the states in this {@link ColorStateList}
580      * @hide
581      */
getStates()582     public int[][] getStates() {
583         return mStateSpecs;
584     }
585 
586     /**
587      * Return the colors in this {@link ColorStateList}. The returned array
588      * should not be modified.
589      *
590      * @return the colors in this {@link ColorStateList}
591      * @hide
592      */
getColors()593     public int[] getColors() {
594         return mColors;
595     }
596 
597     /**
598      * Returns whether the specified state is referenced in any of the state
599      * specs contained within this ColorStateList.
600      * <p>
601      * Any reference, either positive or negative {ex. ~R.attr.state_enabled},
602      * will cause this method to return {@code true}. Wildcards are not counted
603      * as references.
604      *
605      * @param state the state to search for
606      * @return {@code true} if the state if referenced, {@code false} otherwise
607      * @hide Use only as directed. For internal use only.
608      */
hasState(int state)609     public boolean hasState(int state) {
610         final int[][] stateSpecs = mStateSpecs;
611         final int specCount = stateSpecs.length;
612         for (int specIndex = 0; specIndex < specCount; specIndex++) {
613             final int[] states = stateSpecs[specIndex];
614             final int stateCount = states.length;
615             for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) {
616                 if (states[stateIndex] == state || states[stateIndex] == ~state) {
617                     return true;
618                 }
619             }
620         }
621         return false;
622     }
623 
624     @Override
toString()625     public String toString() {
626         return "ColorStateList{" +
627                "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
628                "mChangingConfigurations=" + mChangingConfigurations +
629                "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
630                "mColors=" + Arrays.toString(mColors) +
631                "mDefaultColor=" + mDefaultColor + '}';
632     }
633 
634     /**
635      * Updates the default color and opacity.
636      */
onColorsChanged()637     private void onColorsChanged() {
638         int defaultColor = DEFAULT_COLOR;
639         boolean isOpaque = true;
640 
641         final int[][] states = mStateSpecs;
642         final int[] colors = mColors;
643         final int N = states.length;
644         if (N > 0) {
645             defaultColor = colors[0];
646 
647             for (int i = N - 1; i > 0; i--) {
648                 if (states[i].length == 0) {
649                     defaultColor = colors[i];
650                     break;
651                 }
652             }
653 
654             for (int i = 0; i < N; i++) {
655                 if (Color.alpha(colors[i]) != 0xFF) {
656                     isOpaque = false;
657                     break;
658                 }
659             }
660         }
661 
662         mDefaultColor = defaultColor;
663         mIsOpaque = isOpaque;
664     }
665 
666     /**
667      * @return a factory that can create new instances of this ColorStateList
668      * @hide only for resource preloading
669      */
getConstantState()670     public ConstantState<ComplexColor> getConstantState() {
671         if (mFactory == null) {
672             mFactory = new ColorStateListFactory(this);
673         }
674         return mFactory;
675     }
676 
677     private static class ColorStateListFactory extends ConstantState<ComplexColor> {
678         private final ColorStateList mSrc;
679 
ColorStateListFactory(ColorStateList src)680         public ColorStateListFactory(ColorStateList src) {
681             mSrc = src;
682         }
683 
684         @Override
getChangingConfigurations()685         public @Config int getChangingConfigurations() {
686             return mSrc.mChangingConfigurations;
687         }
688 
689         @Override
newInstance()690         public ColorStateList newInstance() {
691             return mSrc;
692         }
693 
694         @Override
newInstance(Resources res, Theme theme)695         public ColorStateList newInstance(Resources res, Theme theme) {
696             return (ColorStateList) mSrc.obtainForTheme(theme);
697         }
698     }
699 
700     @Override
describeContents()701     public int describeContents() {
702         return 0;
703     }
704 
705     @Override
writeToParcel(Parcel dest, int flags)706     public void writeToParcel(Parcel dest, int flags) {
707         if (canApplyTheme()) {
708             Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
709         }
710         final int N = mStateSpecs.length;
711         dest.writeInt(N);
712         for (int i = 0; i < N; i++) {
713             dest.writeIntArray(mStateSpecs[i]);
714         }
715         dest.writeIntArray(mColors);
716     }
717 
718     public static final Parcelable.Creator<ColorStateList> CREATOR =
719             new Parcelable.Creator<ColorStateList>() {
720         @Override
721         public ColorStateList[] newArray(int size) {
722             return new ColorStateList[size];
723         }
724 
725         @Override
726         public ColorStateList createFromParcel(Parcel source) {
727             final int N = source.readInt();
728             final int[][] stateSpecs = new int[N][];
729             for (int i = 0; i < N; i++) {
730                 stateSpecs[i] = source.createIntArray();
731             }
732             final int[] colors = source.createIntArray();
733             return new ColorStateList(stateSpecs, colors);
734         }
735     };
736 }
737