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 17 package android.content.res; 18 19 import com.android.internal.util.ArrayUtils; 20 21 import org.xmlpull.v1.XmlPullParser; 22 import org.xmlpull.v1.XmlPullParserException; 23 24 import android.util.AttributeSet; 25 import android.util.SparseArray; 26 import android.util.StateSet; 27 import android.util.Xml; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import java.io.IOException; 32 import java.lang.ref.WeakReference; 33 import 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:color="@color/testcolor5"/> 49 * </selector> 50 * </pre> 51 * 52 * This defines a set of state spec / color pairs where each state spec specifies a set of 53 * states that a view must either be in or not be in and the color specifies the color associated 54 * with that spec. The list of state specs will be processed in order of the items in the XML file. 55 * An item with no state spec is considered to match any set of states and is generally useful as 56 * a final item to be used as a default. Note that if you have such an item before any other items 57 * in the list then any subsequent items will end up being ignored. 58 * <p>For more information, see the guide to <a 59 * href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State 60 * List Resource</a>.</p> 61 */ 62 public 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 ColorStateList()72 private ColorStateList() { } 73 74 /** 75 * Creates a ColorStateList that returns the specified mapping from 76 * states to colors. 77 */ ColorStateList(int[][] states, int[] colors)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 */ valueOf(int color)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 */ createFromXml(Resources r, XmlPullParser parser)115 public static ColorStateList createFromXml(Resources r, XmlPullParser parser) 116 throws XmlPullParserException, IOException { 117 118 AttributeSet attrs = Xml.asAttributeSet(parser); 119 120 int type; 121 while ((type=parser.next()) != XmlPullParser.START_TAG 122 && type != XmlPullParser.END_DOCUMENT) { 123 } 124 125 if (type != XmlPullParser.START_TAG) { 126 throw new XmlPullParserException("No start tag found"); 127 } 128 129 return createFromXmlInner(r, parser, attrs); 130 } 131 132 /* Create from inside an XML document. Called on a parser positioned at 133 * a tag in an XML document, tries to create a ColorStateList from that tag. 134 * Returns null if the tag is not a valid ColorStateList. 135 */ createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs)136 private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser, 137 AttributeSet attrs) throws XmlPullParserException, IOException { 138 139 ColorStateList colorStateList; 140 141 final String name = parser.getName(); 142 143 if (name.equals("selector")) { 144 colorStateList = new ColorStateList(); 145 } else { 146 throw new XmlPullParserException( 147 parser.getPositionDescription() + ": invalid drawable tag " + name); 148 } 149 150 colorStateList.inflate(r, parser, attrs); 151 return colorStateList; 152 } 153 154 /** 155 * Creates a new ColorStateList that has the same states and 156 * colors as this one but where each color has the specified alpha value 157 * (0-255). 158 */ withAlpha(int alpha)159 public ColorStateList withAlpha(int alpha) { 160 int[] colors = new int[mColors.length]; 161 162 int len = colors.length; 163 for (int i = 0; i < len; i++) { 164 colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24); 165 } 166 167 return new ColorStateList(mStateSpecs, colors); 168 } 169 170 /** 171 * Fill in this object based on the contents of an XML "selector" element. 172 */ inflate(Resources r, XmlPullParser parser, AttributeSet attrs)173 private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 174 throws XmlPullParserException, IOException { 175 176 int type; 177 178 final int innerDepth = parser.getDepth()+1; 179 int depth; 180 181 int listAllocated = 20; 182 int listSize = 0; 183 int[] colorList = new int[listAllocated]; 184 int[][] stateSpecList = new int[listAllocated][]; 185 186 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 187 && ((depth=parser.getDepth()) >= innerDepth 188 || type != XmlPullParser.END_TAG)) { 189 if (type != XmlPullParser.START_TAG) { 190 continue; 191 } 192 193 if (depth > innerDepth || !parser.getName().equals("item")) { 194 continue; 195 } 196 197 int colorRes = 0; 198 int color = 0xffff0000; 199 boolean haveColor = false; 200 201 int i; 202 int j = 0; 203 final int numAttrs = attrs.getAttributeCount(); 204 int[] stateSpec = new int[numAttrs]; 205 for (i = 0; i < numAttrs; i++) { 206 final int stateResId = attrs.getAttributeNameResource(i); 207 if (stateResId == 0) break; 208 if (stateResId == com.android.internal.R.attr.color) { 209 colorRes = attrs.getAttributeResourceValue(i, 0); 210 211 if (colorRes == 0) { 212 color = attrs.getAttributeIntValue(i, color); 213 haveColor = true; 214 } 215 } else { 216 stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) 217 ? stateResId 218 : -stateResId; 219 } 220 } 221 stateSpec = StateSet.trimStateSet(stateSpec, j); 222 223 if (colorRes != 0) { 224 color = r.getColor(colorRes); 225 } else if (!haveColor) { 226 throw new XmlPullParserException( 227 parser.getPositionDescription() 228 + ": <item> tag requires a 'android:color' attribute."); 229 } 230 231 if (listSize == 0 || stateSpec.length == 0) { 232 mDefaultColor = color; 233 } 234 235 if (listSize + 1 >= listAllocated) { 236 listAllocated = ArrayUtils.idealIntArraySize(listSize + 1); 237 238 int[] ncolor = new int[listAllocated]; 239 System.arraycopy(colorList, 0, ncolor, 0, listSize); 240 241 int[][] nstate = new int[listAllocated][]; 242 System.arraycopy(stateSpecList, 0, nstate, 0, listSize); 243 244 colorList = ncolor; 245 stateSpecList = nstate; 246 } 247 248 colorList[listSize] = color; 249 stateSpecList[listSize] = stateSpec; 250 listSize++; 251 } 252 253 mColors = new int[listSize]; 254 mStateSpecs = new int[listSize][]; 255 System.arraycopy(colorList, 0, mColors, 0, listSize); 256 System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); 257 } 258 isStateful()259 public boolean isStateful() { 260 return mStateSpecs.length > 1; 261 } 262 263 /** 264 * Return the color associated with the given set of {@link android.view.View} states. 265 * 266 * @param stateSet an array of {@link android.view.View} states 267 * @param defaultColor the color to return if there's not state spec in this 268 * {@link ColorStateList} that matches the stateSet. 269 * 270 * @return the color associated with that set of states in this {@link ColorStateList}. 271 */ getColorForState(int[] stateSet, int defaultColor)272 public int getColorForState(int[] stateSet, int defaultColor) { 273 final int setLength = mStateSpecs.length; 274 for (int i = 0; i < setLength; i++) { 275 int[] stateSpec = mStateSpecs[i]; 276 if (StateSet.stateSetMatches(stateSpec, stateSet)) { 277 return mColors[i]; 278 } 279 } 280 return defaultColor; 281 } 282 283 /** 284 * Return the default color in this {@link ColorStateList}. 285 * 286 * @return the default color in this {@link ColorStateList}. 287 */ getDefaultColor()288 public int getDefaultColor() { 289 return mDefaultColor; 290 } 291 toString()292 public String toString() { 293 return "ColorStateList{" + 294 "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + 295 "mColors=" + Arrays.toString(mColors) + 296 "mDefaultColor=" + mDefaultColor + '}'; 297 } 298 describeContents()299 public int describeContents() { 300 return 0; 301 } 302 writeToParcel(Parcel dest, int flags)303 public void writeToParcel(Parcel dest, int flags) { 304 final int N = mStateSpecs.length; 305 dest.writeInt(N); 306 for (int i=0; i<N; i++) { 307 dest.writeIntArray(mStateSpecs[i]); 308 } 309 dest.writeIntArray(mColors); 310 } 311 312 public static final Parcelable.Creator<ColorStateList> CREATOR = 313 new Parcelable.Creator<ColorStateList>() { 314 public ColorStateList[] newArray(int size) { 315 return new ColorStateList[size]; 316 } 317 318 public ColorStateList createFromParcel(Parcel source) { 319 final int N = source.readInt(); 320 int[][] stateSpecs = new int[N][]; 321 for (int i=0; i<N; i++) { 322 stateSpecs[i] = source.createIntArray(); 323 } 324 int[] colors = source.createIntArray(); 325 return new ColorStateList(stateSpecs, colors); 326 } 327 }; 328 } 329