• 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.graphics.Color;
20 
21 import com.android.internal.util.ArrayUtils;
22 import com.android.internal.util.GrowingArrayUtils;
23 
24 import org.xmlpull.v1.XmlPullParser;
25 import org.xmlpull.v1.XmlPullParserException;
26 
27 import android.util.AttributeSet;
28 import android.util.MathUtils;
29 import android.util.SparseArray;
30 import android.util.StateSet;
31 import android.util.Xml;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 
35 import java.io.IOException;
36 import java.lang.ref.WeakReference;
37 import java.util.Arrays;
38 
39 /**
40  *
41  * Lets you map {@link android.view.View} state sets to colors.
42  *
43  * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
44  * "color" subdirectory directory of an application's resource directory.  The XML file contains
45  * a single "selector" element with a number of "item" elements inside.  For example:
46  *
47  * <pre>
48  * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
49  *   &lt;item android:state_focused="true" android:color="@color/testcolor1"/&gt;
50  *   &lt;item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /&gt;
51  *   &lt;item android:state_enabled="false" android:color="@color/testcolor3" /&gt;
52  *   &lt;item android:color="@color/testcolor5"/&gt;
53  * &lt;/selector&gt;
54  * </pre>
55  *
56  * This defines a set of state spec / color pairs where each state spec specifies a set of
57  * states that a view must either be in or not be in and the color specifies the color associated
58  * with that spec.  The list of state specs will be processed in order of the items in the XML file.
59  * An item with no state spec is considered to match any set of states and is generally useful as
60  * a final item to be used as a default.  Note that if you have such an item before any other items
61  * in the list then any subsequent items will end up being ignored.
62  * <p>For more information, see the guide to <a
63  * href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State
64  * List Resource</a>.</p>
65  */
66 public class ColorStateList implements Parcelable {
67     private int[][] mStateSpecs; // must be parallel to mColors
68     private int[] mColors;      // must be parallel to mStateSpecs
69     private int mDefaultColor = 0xffff0000;
70 
71     private static final int[][] EMPTY = new int[][] { new int[0] };
72     private static final SparseArray<WeakReference<ColorStateList>> sCache =
73                             new SparseArray<WeakReference<ColorStateList>>();
74 
ColorStateList()75     private ColorStateList() { }
76 
77     /**
78      * Creates a ColorStateList that returns the specified mapping from
79      * states to colors.
80      */
ColorStateList(int[][] states, int[] colors)81     public ColorStateList(int[][] states, int[] colors) {
82         mStateSpecs = states;
83         mColors = colors;
84 
85         if (states.length > 0) {
86             mDefaultColor = colors[0];
87 
88             for (int i = 0; i < states.length; i++) {
89                 if (states[i].length == 0) {
90                     mDefaultColor = colors[i];
91                 }
92             }
93         }
94     }
95 
96     /**
97      * Creates or retrieves a ColorStateList that always returns a single color.
98      */
valueOf(int color)99     public static ColorStateList valueOf(int color) {
100         // TODO: should we collect these eventually?
101         synchronized (sCache) {
102             final WeakReference<ColorStateList> ref = sCache.get(color);
103 
104             ColorStateList csl = ref != null ? ref.get() : null;
105             if (csl != null) {
106                 return csl;
107             }
108 
109             csl = new ColorStateList(EMPTY, new int[] { color });
110             sCache.put(color, new WeakReference<ColorStateList>(csl));
111             return csl;
112         }
113     }
114 
115     /**
116      * Create a ColorStateList from an XML document, given a set of {@link Resources}.
117      */
createFromXml(Resources r, XmlPullParser parser)118     public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
119             throws XmlPullParserException, IOException {
120         final AttributeSet attrs = Xml.asAttributeSet(parser);
121 
122         int type;
123         while ((type=parser.next()) != XmlPullParser.START_TAG
124                    && type != XmlPullParser.END_DOCUMENT) {
125         }
126 
127         if (type != XmlPullParser.START_TAG) {
128             throw new XmlPullParserException("No start tag found");
129         }
130 
131         return createFromXmlInner(r, parser, attrs);
132     }
133 
134     /**
135      * Create from inside an XML document. Called on a parser positioned at a
136      * tag in an XML document, tries to create a ColorStateList from that tag.
137      *
138      * @throws XmlPullParserException if the current tag is not &lt;selector>
139      * @return A color state list for the current tag.
140      */
createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)141     private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
142             AttributeSet attrs) throws XmlPullParserException, IOException {
143         final ColorStateList colorStateList;
144         final String name = parser.getName();
145         if (name.equals("selector")) {
146             colorStateList = new ColorStateList();
147         } else {
148             throw new XmlPullParserException(
149                     parser.getPositionDescription() + ": invalid drawable tag " + name);
150         }
151 
152         colorStateList.inflate(r, parser, attrs);
153         return colorStateList;
154     }
155 
156     /**
157      * Creates a new ColorStateList that has the same states and
158      * colors as this one but where each color has the specified alpha value
159      * (0-255).
160      */
withAlpha(int alpha)161     public ColorStateList withAlpha(int alpha) {
162         final int[] colors = new int[mColors.length];
163         final int len = colors.length;
164         for (int i = 0; i < len; i++) {
165             colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
166         }
167 
168         return new ColorStateList(mStateSpecs, colors);
169     }
170 
171     /**
172      * Fill in this object based on the contents of an XML "selector" element.
173      */
inflate(Resources r, XmlPullParser parser, AttributeSet attrs)174     private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
175             throws XmlPullParserException, IOException {
176         int type;
177 
178         final int innerDepth = parser.getDepth()+1;
179         int depth;
180 
181         int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
182         int[] colorList = new int[stateSpecList.length];
183         int listSize = 0;
184 
185         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
186                && ((depth=parser.getDepth()) >= innerDepth
187                    || type != XmlPullParser.END_TAG)) {
188             if (type != XmlPullParser.START_TAG) {
189                 continue;
190             }
191 
192             if (depth > innerDepth || !parser.getName().equals("item")) {
193                 continue;
194             }
195 
196             int alphaRes = 0;
197             float alpha = 1.0f;
198             int colorRes = 0;
199             int color = 0xffff0000;
200             boolean haveColor = false;
201 
202             int i;
203             int j = 0;
204             final int numAttrs = attrs.getAttributeCount();
205             int[] stateSpec = new int[numAttrs];
206             for (i = 0; i < numAttrs; i++) {
207                 final int stateResId = attrs.getAttributeNameResource(i);
208                 if (stateResId == 0) break;
209                 if (stateResId == com.android.internal.R.attr.alpha) {
210                     alphaRes = attrs.getAttributeResourceValue(i, 0);
211                     if (alphaRes == 0) {
212                         alpha = attrs.getAttributeFloatValue(i, 1.0f);
213                     }
214                 } else if (stateResId == com.android.internal.R.attr.color) {
215                     colorRes = attrs.getAttributeResourceValue(i, 0);
216                     if (colorRes == 0) {
217                         color = attrs.getAttributeIntValue(i, color);
218                         haveColor = true;
219                     }
220                 } else {
221                     stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
222                             ? stateResId : -stateResId;
223                 }
224             }
225             stateSpec = StateSet.trimStateSet(stateSpec, j);
226 
227             if (colorRes != 0) {
228                 color = r.getColor(colorRes);
229             } else if (!haveColor) {
230                 throw new XmlPullParserException(
231                         parser.getPositionDescription()
232                         + ": <item> tag requires a 'android:color' attribute.");
233             }
234 
235             if (alphaRes != 0) {
236                 alpha = r.getFloat(alphaRes);
237             }
238 
239             // Apply alpha modulation.
240             final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255);
241             color = (color & 0xFFFFFF) | (alphaMod << 24);
242 
243             if (listSize == 0 || stateSpec.length == 0) {
244                 mDefaultColor = color;
245             }
246 
247             colorList = GrowingArrayUtils.append(colorList, listSize, color);
248             stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
249             listSize++;
250         }
251 
252         mColors = new int[listSize];
253         mStateSpecs = new int[listSize][];
254         System.arraycopy(colorList, 0, mColors, 0, listSize);
255         System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
256     }
257 
258     /**
259      * Indicates whether this color state list contains more than one state spec
260      * and will change color based on state.
261      *
262      * @return True if this color state list changes color based on state, false
263      *         otherwise.
264      * @see #getColorForState(int[], int)
265      */
isStateful()266     public boolean isStateful() {
267         return mStateSpecs.length > 1;
268     }
269 
270     /**
271      * Indicates whether this color state list is opaque, which means that every
272      * color returned from {@link #getColorForState(int[], int)} has an alpha
273      * value of 255.
274      *
275      * @return True if this color state list is opaque.
276      */
isOpaque()277     public boolean isOpaque() {
278         final int n = mColors.length;
279         for (int i = 0; i < n; i++) {
280             if (Color.alpha(mColors[i]) != 0xFF) {
281                 return false;
282             }
283         }
284         return true;
285     }
286 
287     /**
288      * Return the color associated with the given set of {@link android.view.View} states.
289      *
290      * @param stateSet an array of {@link android.view.View} states
291      * @param defaultColor the color to return if there's not state spec in this
292      * {@link ColorStateList} that matches the stateSet.
293      *
294      * @return the color associated with that set of states in this {@link ColorStateList}.
295      */
getColorForState(int[] stateSet, int defaultColor)296     public int getColorForState(int[] stateSet, int defaultColor) {
297         final int setLength = mStateSpecs.length;
298         for (int i = 0; i < setLength; i++) {
299             int[] stateSpec = mStateSpecs[i];
300             if (StateSet.stateSetMatches(stateSpec, stateSet)) {
301                 return mColors[i];
302             }
303         }
304         return defaultColor;
305     }
306 
307     /**
308      * Return the default color in this {@link ColorStateList}.
309      *
310      * @return the default color in this {@link ColorStateList}.
311      */
getDefaultColor()312     public int getDefaultColor() {
313         return mDefaultColor;
314     }
315 
316     /**
317      * Return the states in this {@link ColorStateList}.
318      * @return the states in this {@link ColorStateList}
319      * @hide
320      */
getStates()321     public int[][] getStates() {
322         return mStateSpecs;
323     }
324 
325     /**
326      * Return the colors in this {@link ColorStateList}.
327      * @return the colors in this {@link ColorStateList}
328      * @hide
329      */
getColors()330     public int[] getColors() {
331         return mColors;
332     }
333 
334     /**
335      * If the color state list does not already have an entry matching the
336      * specified state, prepends a state set and color pair to a color state
337      * list.
338      * <p>
339      * This is a workaround used in TimePicker and DatePicker until we can
340      * add support for theme attributes in ColorStateList.
341      *
342      * @param colorStateList the source color state list
343      * @param state the state to prepend
344      * @param color the color to use for the given state
345      * @return a new color state list, or the source color state list if there
346      *         was already a matching state set
347      *
348      * @hide Remove when we can support theme attributes.
349      */
addFirstIfMissing( ColorStateList colorStateList, int state, int color)350     public static ColorStateList addFirstIfMissing(
351             ColorStateList colorStateList, int state, int color) {
352         final int[][] inputStates = colorStateList.getStates();
353         for (int i = 0; i < inputStates.length; i++) {
354             final int[] inputState = inputStates[i];
355             for (int j = 0; j < inputState.length; j++) {
356                 if (inputState[j] == state) {
357                     return colorStateList;
358                 }
359             }
360         }
361 
362         final int[][] outputStates = new int[inputStates.length + 1][];
363         System.arraycopy(inputStates, 0, outputStates, 1, inputStates.length);
364         outputStates[0] = new int[] { state };
365 
366         final int[] inputColors = colorStateList.getColors();
367         final int[] outputColors = new int[inputColors.length + 1];
368         System.arraycopy(inputColors, 0, outputColors, 1, inputColors.length);
369         outputColors[0] = color;
370 
371         return new ColorStateList(outputStates, outputColors);
372     }
373 
374     @Override
toString()375     public String toString() {
376         return "ColorStateList{" +
377                "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
378                "mColors=" + Arrays.toString(mColors) +
379                "mDefaultColor=" + mDefaultColor + '}';
380     }
381 
382     @Override
describeContents()383     public int describeContents() {
384         return 0;
385     }
386 
387     @Override
writeToParcel(Parcel dest, int flags)388     public void writeToParcel(Parcel dest, int flags) {
389         final int N = mStateSpecs.length;
390         dest.writeInt(N);
391         for (int i = 0; i < N; i++) {
392             dest.writeIntArray(mStateSpecs[i]);
393         }
394         dest.writeIntArray(mColors);
395     }
396 
397     public static final Parcelable.Creator<ColorStateList> CREATOR =
398             new Parcelable.Creator<ColorStateList>() {
399         @Override
400         public ColorStateList[] newArray(int size) {
401             return new ColorStateList[size];
402         }
403 
404         @Override
405         public ColorStateList createFromParcel(Parcel source) {
406             final int N = source.readInt();
407             final int[][] stateSpecs = new int[N][];
408             for (int i = 0; i < N; i++) {
409                 stateSpecs[i] = source.createIntArray();
410             }
411             final int[] colors = source.createIntArray();
412             return new ColorStateList(stateSpecs, colors);
413         }
414     };
415 }
416