blob: bd23db46a1ee920a1a29ab543858710c428d8858 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.content.res;
18
19import com.android.internal.util.ArrayUtils;
20
21import org.xmlpull.v1.XmlPullParser;
22import org.xmlpull.v1.XmlPullParserException;
23
24import android.util.AttributeSet;
25import android.util.SparseArray;
26import android.util.StateSet;
27import android.util.Xml;
28import android.os.Parcel;
29import android.os.Parcelable;
30
31import java.io.IOException;
32import java.lang.ref.WeakReference;
33import 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;
Romain Guy79dc8cc2010-01-04 14:45:49 -080047 * &lt;item android:state_enabled="false" android:color="@color/testcolor3" /&gt;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048 * &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.
Scott Main8dd87ad2010-08-04 17:33:33 -070058 * <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>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 */
62public 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
72 private ColorStateList() { }
73
74 /**
75 * Creates a ColorStateList that returns the specified mapping from
76 * states to colors.
77 */
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 */
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 */
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 */
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 */
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 */
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
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 */
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 */
288 public int getDefaultColor() {
289 return mDefaultColor;
290 }
291
292 public String toString() {
293 return "ColorStateList{" +
294 "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
295 "mColors=" + Arrays.toString(mColors) +
296 "mDefaultColor=" + mDefaultColor + '}';
297 }
298
299 public int describeContents() {
300 return 0;
301 }
302
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 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}