ColorStateList.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.google.android.collect.Lists; 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.SparseArray; 28import android.util.StateSet; 29import android.util.Xml; 30import android.os.Parcel; 31import android.os.Parcelable; 32 33import java.io.IOException; 34import java.lang.ref.WeakReference; 35import java.util.Arrays; 36 37/** 38 * 39 * Lets you map {@link android.view.View} state sets to colors. 40 * 41 * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the 42 * "color" subdirectory directory of an application's resource directory. The XML file contains 43 * a single "selector" element with a number of "item" elements inside. For example: 44 * 45 * <pre> 46 * <selector xmlns:android="http://schemas.android.com/apk/res/android"> 47 * <item android:state_focused="true" android:color="@color/testcolor1"/> 48 * <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /> 49 * <item android:state_enabled="false" android:colore="@color/testcolor3" /> 50 * <item android:state_active="true" android:color="@color/testcolor4" /> 51 * <item android:color="@color/testcolor5"/> 52 * </selector> 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 */ 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 AttributeSet attrs = Xml.asAttributeSet(parser); 118 119 int type; 120 while ((type=parser.next()) != XmlPullParser.START_TAG 121 && type != XmlPullParser.END_DOCUMENT) { 122 } 123 124 if (type != XmlPullParser.START_TAG) { 125 throw new XmlPullParserException("No start tag found"); 126 } 127 128 final ColorStateList colorStateList = createFromXmlInner(r, parser, attrs); 129 130 return colorStateList; 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, 138 XmlPullParser parser, 139 AttributeSet attrs) 140 throws XmlPullParserException, IOException { 141 ColorStateList colorStateList; 142 143 final String name = parser.getName(); 144 145 if (name.equals("selector")) { 146 colorStateList = new ColorStateList(); 147 } else { 148 throw new XmlPullParserException( 149 parser.getPositionDescription() + ": invalid drawable tag " 150 + 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 colorRes = 0; 201 int color = 0xffff0000; 202 boolean haveColor = false; 203 204 int i; 205 int j = 0; 206 final int numAttrs = attrs.getAttributeCount(); 207 int[] stateSpec = new int[numAttrs]; 208 for (i = 0; i < numAttrs; i++) { 209 final int stateResId = attrs.getAttributeNameResource(i); 210 if (stateResId == 0) break; 211 if (stateResId == com.android.internal.R.attr.color) { 212 colorRes = attrs.getAttributeResourceValue(i, 0); 213 214 if (colorRes == 0) { 215 color = attrs.getAttributeIntValue(i, color); 216 haveColor = true; 217 } 218 } else { 219 stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) 220 ? stateResId 221 : -stateResId; 222 } 223 } 224 stateSpec = StateSet.trimStateSet(stateSpec, j); 225 226 if (colorRes != 0) { 227 color = r.getColor(colorRes); 228 } else if (!haveColor) { 229 throw new XmlPullParserException( 230 parser.getPositionDescription() 231 + ": <item> tag requires a 'android:color' attribute."); 232 } 233 234 if (listSize == 0 || stateSpec.length == 0) { 235 mDefaultColor = color; 236 } 237 238 if (listSize + 1 >= listAllocated) { 239 listAllocated = ArrayUtils.idealIntArraySize(listSize + 1); 240 241 int[] ncolor = new int[listAllocated]; 242 System.arraycopy(colorList, 0, ncolor, 0, listSize); 243 244 int[][] nstate = new int[listAllocated][]; 245 System.arraycopy(stateSpecList, 0, nstate, 0, listSize); 246 247 colorList = ncolor; 248 stateSpecList = nstate; 249 } 250 251 colorList[listSize] = color; 252 stateSpecList[listSize] = stateSpec; 253 listSize++; 254 } 255 256 mColors = new int[listSize]; 257 mStateSpecs = new int[listSize][]; 258 System.arraycopy(colorList, 0, mColors, 0, listSize); 259 System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); 260 } 261 262 public boolean isStateful() { 263 return mStateSpecs.length > 1; 264 } 265 266 /** 267 * Return the color associated with the given set of {@link android.view.View} states. 268 * 269 * @param stateSet an array of {@link android.view.View} states 270 * @param defaultColor the color to return if there's not state spec in this 271 * {@link ColorStateList} that matches the stateSet. 272 * 273 * @return the color associated with that set of states in this {@link ColorStateList}. 274 */ 275 public int getColorForState(int[] stateSet, int defaultColor) { 276 final int setLength = mStateSpecs.length; 277 for (int i = 0; i < setLength; i++) { 278 int[] stateSpec = mStateSpecs[i]; 279 if (StateSet.stateSetMatches(stateSpec, stateSet)) { 280 return mColors[i]; 281 } 282 } 283 return defaultColor; 284 } 285 286 /** 287 * Return the default color in this {@link ColorStateList}. 288 * 289 * @return the default color in this {@link ColorStateList}. 290 */ 291 public int getDefaultColor() { 292 return mDefaultColor; 293 } 294 295 public String toString() { 296 return "ColorStateList{" + 297 "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + 298 "mColors=" + Arrays.toString(mColors) + 299 "mDefaultColor=" + mDefaultColor + '}'; 300 } 301 302 public int describeContents() { 303 return 0; 304 } 305 306 public void writeToParcel(Parcel dest, int flags) { 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 Object[] o = source.readArray( 319 ColorStateList.class.getClassLoader()); 320 int[][] stateSpecs = new int[o.length][]; 321 322 for (int i = 0; i < o.length; i++) { 323 stateSpecs[i] = (int[]) o[i]; 324 } 325 326 int[] colors = source.createIntArray(); 327 return new ColorStateList(stateSpecs, colors); 328 } 329 }; 330} 331