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 * <selector xmlns:android="http://schemas.android.com/apk/res/android"> 48 * <item android:state_focused="true" android:color="@color/testcolor1"/> 49 * <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /> 50 * <item android:state_enabled="false" android:color="@color/testcolor3" /> 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 * <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