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 * <selector xmlns:android="http://schemas.android.com/apk/res/android"> 45 * <item android:state_focused="true" android:color="@color/testcolor1"/> 46 * <item android:state_pressed="true" android:state_enabled="false" android:color="@color/testcolor2" /> 47 * <item android:state_enabled="false" android:color="@color/testcolor3" /> 48 * <item android:state_active="true" android:color="@color/testcolor4" /> 49 * <item android:color="@color/testcolor5"/> 50 * </selector> 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.writeIntArray(mColors); 308 } 309 310 public static final Parcelable.Creator<ColorStateList> CREATOR = 311 new Parcelable.Creator<ColorStateList>() { 312 public ColorStateList[] newArray(int size) { 313 return new ColorStateList[size]; 314 } 315 316 public ColorStateList createFromParcel(Parcel source) { 317 final int N = source.readInt(); 318 int[][] stateSpecs = new int[N][]; 319 for (int i=0; i<N; i++) { 320 stateSpecs[i] = source.createIntArray(); 321 } 322 int[] colors = source.createIntArray(); 323 return new ColorStateList(stateSpecs, colors); 324 } 325 }; 326} 327