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