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