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