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