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 android.annotation.ColorInt; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.pm.ActivityInfo.Config; 23 import android.content.res.Resources.Theme; 24 import android.graphics.Color; 25 26 import com.android.internal.R; 27 import com.android.internal.util.ArrayUtils; 28 import com.android.internal.util.GrowingArrayUtils; 29 30 import org.xmlpull.v1.XmlPullParser; 31 import org.xmlpull.v1.XmlPullParserException; 32 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.util.MathUtils; 36 import android.util.SparseArray; 37 import android.util.StateSet; 38 import android.util.Xml; 39 import android.os.Parcel; 40 import android.os.Parcelable; 41 42 import java.io.IOException; 43 import java.lang.ref.WeakReference; 44 import java.util.Arrays; 45 46 /** 47 * 48 * Lets you map {@link android.view.View} state sets to colors. 49 * <p> 50 * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the 51 * "color" subdirectory directory of an application's resource directory. The XML file contains 52 * a single "selector" element with a number of "item" elements inside. For example: 53 * <pre> 54 * <selector xmlns:android="http://schemas.android.com/apk/res/android"> 55 * <item android:state_focused="true" 56 * android:color="@color/sample_focused" /> 57 * <item android:state_pressed="true" 58 * android:state_enabled="false" 59 * android:color="@color/sample_disabled_pressed" /> 60 * <item android:state_enabled="false" 61 * android:color="@color/sample_disabled_not_pressed" /> 62 * <item android:color="@color/sample_default" /> 63 * </selector> 64 * </pre> 65 * 66 * This defines a set of state spec / color pairs where each state spec specifies a set of 67 * states that a view must either be in or not be in and the color specifies the color associated 68 * with that spec. 69 * 70 * <a name="StateSpec"></a> 71 * <h3>State specs</h3> 72 * <p> 73 * Each item defines a set of state spec and color pairs, where the state spec is a series of 74 * attributes set to either {@code true} or {@code false} to represent inclusion or exclusion. If 75 * an attribute is not specified for an item, it may be any value. 76 * <p> 77 * For example, the following item will be matched whenever the focused state is set; any other 78 * states may be set or unset: 79 * <pre> 80 * <item android:state_focused="true" 81 * android:color="@color/sample_focused" /> 82 * </pre> 83 * <p> 84 * Typically, a color state list will reference framework-defined state attributes such as 85 * {@link android.R.attr#state_focused android:state_focused} or 86 * {@link android.R.attr#state_enabled android:state_enabled}; however, app-defined attributes may 87 * also be used. 88 * <p> 89 * <strong>Note:</strong> The list of state specs will be matched against in the order that they 90 * appear in the XML file. For this reason, more-specific items should be placed earlier in the 91 * file. An item with no state spec is considered to match any set of states and is generally 92 * useful as a final item to be used as a default. 93 * <p> 94 * If an item with no state spec if placed before other items, those items 95 * will be ignored. 96 * 97 * <a name="ItemAttributes"></a> 98 * <h3>Item attributes</h3> 99 * <p> 100 * Each item must define an {@link android.R.attr#color android:color} attribute, which may be 101 * an HTML-style hex color, a reference to a color resource, or -- in API 23 and above -- a theme 102 * attribute that resolves to a color. 103 * <p> 104 * Starting with API 23, items may optionally define an {@link android.R.attr#alpha android:alpha} 105 * attribute to modify the base color's opacity. This attribute takes a either floating-point value 106 * between 0 and 1 or a theme attribute that resolves as such. The item's overall color is 107 * calculated by multiplying by the base color's alpha channel by the {@code alpha} value. For 108 * example, the following item represents the theme's accent color at 50% opacity: 109 * <pre> 110 * <item android:state_enabled="false" 111 * android:color="?android:attr/colorAccent" 112 * android:alpha="0.5" /> 113 * </pre> 114 * 115 * <a name="DeveloperGuide"></a> 116 * <h3>Developer guide</h3> 117 * <p> 118 * For more information, see the guide to 119 * <a href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State 120 * List Resource</a>. 121 * 122 * @attr ref android.R.styleable#ColorStateListItem_alpha 123 * @attr ref android.R.styleable#ColorStateListItem_color 124 */ 125 public class ColorStateList extends ComplexColor implements Parcelable { 126 private static final String TAG = "ColorStateList"; 127 128 private static final int DEFAULT_COLOR = Color.RED; 129 private static final int[][] EMPTY = new int[][] { new int[0] }; 130 131 /** Thread-safe cache of single-color ColorStateLists. */ 132 private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>(); 133 134 /** Lazily-created factory for this color state list. */ 135 private ColorStateListFactory mFactory; 136 137 private int[][] mThemeAttrs; 138 private @Config int mChangingConfigurations; 139 140 private int[][] mStateSpecs; 141 private int[] mColors; 142 private int mDefaultColor; 143 private boolean mIsOpaque; 144 ColorStateList()145 private ColorStateList() { 146 // Not publicly instantiable. 147 } 148 149 /** 150 * Creates a ColorStateList that returns the specified mapping from 151 * states to colors. 152 */ ColorStateList(int[][] states, @ColorInt int[] colors)153 public ColorStateList(int[][] states, @ColorInt int[] colors) { 154 mStateSpecs = states; 155 mColors = colors; 156 157 onColorsChanged(); 158 } 159 160 /** 161 * @return A ColorStateList containing a single color. 162 */ 163 @NonNull valueOf(@olorInt int color)164 public static ColorStateList valueOf(@ColorInt int color) { 165 synchronized (sCache) { 166 final int index = sCache.indexOfKey(color); 167 if (index >= 0) { 168 final ColorStateList cached = sCache.valueAt(index).get(); 169 if (cached != null) { 170 return cached; 171 } 172 173 // Prune missing entry. 174 sCache.removeAt(index); 175 } 176 177 // Prune the cache before adding new items. 178 final int N = sCache.size(); 179 for (int i = N - 1; i >= 0; i--) { 180 if (sCache.valueAt(i).get() == null) { 181 sCache.removeAt(i); 182 } 183 } 184 185 final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color }); 186 sCache.put(color, new WeakReference<>(csl)); 187 return csl; 188 } 189 } 190 191 /** 192 * Creates a ColorStateList with the same properties as another 193 * ColorStateList. 194 * <p> 195 * The properties of the new ColorStateList can be modified without 196 * affecting the source ColorStateList. 197 * 198 * @param orig the source color state list 199 */ ColorStateList(ColorStateList orig)200 private ColorStateList(ColorStateList orig) { 201 if (orig != null) { 202 mChangingConfigurations = orig.mChangingConfigurations; 203 mStateSpecs = orig.mStateSpecs; 204 mDefaultColor = orig.mDefaultColor; 205 mIsOpaque = orig.mIsOpaque; 206 207 // Deep copy, these may change due to applyTheme(). 208 mThemeAttrs = orig.mThemeAttrs.clone(); 209 mColors = orig.mColors.clone(); 210 } 211 } 212 213 /** 214 * Creates a ColorStateList from an XML document. 215 * 216 * @param r Resources against which the ColorStateList should be inflated. 217 * @param parser Parser for the XML document defining the ColorStateList. 218 * @return A new color state list. 219 * 220 * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme) 221 */ 222 @NonNull 223 @Deprecated createFromXml(Resources r, XmlPullParser parser)224 public static ColorStateList createFromXml(Resources r, XmlPullParser parser) 225 throws XmlPullParserException, IOException { 226 return createFromXml(r, parser, null); 227 } 228 229 /** 230 * Creates a ColorStateList from an XML document using given a set of 231 * {@link Resources} and a {@link Theme}. 232 * 233 * @param r Resources against which the ColorStateList should be inflated. 234 * @param parser Parser for the XML document defining the ColorStateList. 235 * @param theme Optional theme to apply to the color state list, may be 236 * {@code null}. 237 * @return A new color state list. 238 */ 239 @NonNull createFromXml(@onNull Resources r, @NonNull XmlPullParser parser, @Nullable Theme theme)240 public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser, 241 @Nullable Theme theme) throws XmlPullParserException, IOException { 242 final AttributeSet attrs = Xml.asAttributeSet(parser); 243 244 int type; 245 while ((type = parser.next()) != XmlPullParser.START_TAG 246 && type != XmlPullParser.END_DOCUMENT) { 247 // Seek parser to start tag. 248 } 249 250 if (type != XmlPullParser.START_TAG) { 251 throw new XmlPullParserException("No start tag found"); 252 } 253 254 return createFromXmlInner(r, parser, attrs, theme); 255 } 256 257 /** 258 * Create from inside an XML document. Called on a parser positioned at a 259 * tag in an XML document, tries to create a ColorStateList from that tag. 260 * 261 * @throws XmlPullParserException if the current tag is not <selector> 262 * @return A new color state list for the current tag. 263 */ 264 @NonNull createFromXmlInner(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)265 static ColorStateList createFromXmlInner(@NonNull Resources r, 266 @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) 267 throws XmlPullParserException, IOException { 268 final String name = parser.getName(); 269 if (!name.equals("selector")) { 270 throw new XmlPullParserException( 271 parser.getPositionDescription() + ": invalid color state list tag " + name); 272 } 273 274 final ColorStateList colorStateList = new ColorStateList(); 275 colorStateList.inflate(r, parser, attrs, theme); 276 return colorStateList; 277 } 278 279 /** 280 * Creates a new ColorStateList that has the same states and colors as this 281 * one but where each color has the specified alpha value (0-255). 282 * 283 * @param alpha The new alpha channel value (0-255). 284 * @return A new color state list. 285 */ 286 @NonNull withAlpha(int alpha)287 public ColorStateList withAlpha(int alpha) { 288 final int[] colors = new int[mColors.length]; 289 final int len = colors.length; 290 for (int i = 0; i < len; i++) { 291 colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24); 292 } 293 294 return new ColorStateList(mStateSpecs, colors); 295 } 296 297 /** 298 * Fill in this object based on the contents of an XML "selector" element. 299 */ inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)300 private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 301 @NonNull AttributeSet attrs, @Nullable Theme theme) 302 throws XmlPullParserException, IOException { 303 final int innerDepth = parser.getDepth()+1; 304 int depth; 305 int type; 306 307 @Config int changingConfigurations = 0; 308 int defaultColor = DEFAULT_COLOR; 309 310 boolean hasUnresolvedAttrs = false; 311 312 int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20); 313 int[][] themeAttrsList = new int[stateSpecList.length][]; 314 int[] colorList = new int[stateSpecList.length]; 315 int listSize = 0; 316 317 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 318 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 319 if (type != XmlPullParser.START_TAG || depth > innerDepth 320 || !parser.getName().equals("item")) { 321 continue; 322 } 323 324 final TypedArray a = Resources.obtainAttributes(r, theme, attrs, 325 R.styleable.ColorStateListItem); 326 final int[] themeAttrs = a.extractThemeAttrs(); 327 final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA); 328 final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f); 329 330 changingConfigurations |= a.getChangingConfigurations(); 331 332 a.recycle(); 333 334 // Parse all unrecognized attributes as state specifiers. 335 int j = 0; 336 final int numAttrs = attrs.getAttributeCount(); 337 int[] stateSpec = new int[numAttrs]; 338 for (int i = 0; i < numAttrs; i++) { 339 final int stateResId = attrs.getAttributeNameResource(i); 340 switch (stateResId) { 341 case R.attr.color: 342 case R.attr.alpha: 343 // Recognized attribute, ignore. 344 break; 345 default: 346 stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) 347 ? stateResId : -stateResId; 348 } 349 } 350 stateSpec = StateSet.trimStateSet(stateSpec, j); 351 352 // Apply alpha modulation. If we couldn't resolve the color or 353 // alpha yet, the default values leave us enough information to 354 // modulate again during applyTheme(). 355 final int color = modulateColorAlpha(baseColor, alphaMod); 356 if (listSize == 0 || stateSpec.length == 0) { 357 defaultColor = color; 358 } 359 360 if (themeAttrs != null) { 361 hasUnresolvedAttrs = true; 362 } 363 364 colorList = GrowingArrayUtils.append(colorList, listSize, color); 365 themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs); 366 stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); 367 listSize++; 368 } 369 370 mChangingConfigurations = changingConfigurations; 371 mDefaultColor = defaultColor; 372 373 if (hasUnresolvedAttrs) { 374 mThemeAttrs = new int[listSize][]; 375 System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize); 376 } else { 377 mThemeAttrs = null; 378 } 379 380 mColors = new int[listSize]; 381 mStateSpecs = new int[listSize][]; 382 System.arraycopy(colorList, 0, mColors, 0, listSize); 383 System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); 384 385 onColorsChanged(); 386 } 387 388 /** 389 * Returns whether a theme can be applied to this color state list, which 390 * usually indicates that the color state list has unresolved theme 391 * attributes. 392 * 393 * @return whether a theme can be applied to this color state list 394 * @hide only for resource preloading 395 */ 396 @Override canApplyTheme()397 public boolean canApplyTheme() { 398 return mThemeAttrs != null; 399 } 400 401 /** 402 * Applies a theme to this color state list. 403 * <p> 404 * <strong>Note:</strong> Applying a theme may affect the changing 405 * configuration parameters of this color state list. After calling this 406 * method, any dependent configurations must be updated by obtaining the 407 * new configuration mask from {@link #getChangingConfigurations()}. 408 * 409 * @param t the theme to apply 410 */ applyTheme(Theme t)411 private void applyTheme(Theme t) { 412 if (mThemeAttrs == null) { 413 return; 414 } 415 416 boolean hasUnresolvedAttrs = false; 417 418 final int[][] themeAttrsList = mThemeAttrs; 419 final int N = themeAttrsList.length; 420 for (int i = 0; i < N; i++) { 421 if (themeAttrsList[i] != null) { 422 final TypedArray a = t.resolveAttributes(themeAttrsList[i], 423 R.styleable.ColorStateListItem); 424 425 final float defaultAlphaMod; 426 if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) { 427 // If the base color hasn't been resolved yet, the current 428 // color's alpha channel is either full-opacity (if we 429 // haven't resolved the alpha modulation yet) or 430 // pre-modulated. Either is okay as a default value. 431 defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f; 432 } else { 433 // Otherwise, the only correct default value is 1. Even if 434 // nothing is resolved during this call, we can apply this 435 // multiple times without losing of information. 436 defaultAlphaMod = 1.0f; 437 } 438 439 // Extract the theme attributes, if any, before attempting to 440 // read from the typed array. This prevents a crash if we have 441 // unresolved attrs. 442 themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); 443 if (themeAttrsList[i] != null) { 444 hasUnresolvedAttrs = true; 445 } 446 447 final int baseColor = a.getColor( 448 R.styleable.ColorStateListItem_color, mColors[i]); 449 final float alphaMod = a.getFloat( 450 R.styleable.ColorStateListItem_alpha, defaultAlphaMod); 451 mColors[i] = modulateColorAlpha(baseColor, alphaMod); 452 453 // Account for any configuration changes. 454 mChangingConfigurations |= a.getChangingConfigurations(); 455 456 a.recycle(); 457 } 458 } 459 460 if (!hasUnresolvedAttrs) { 461 mThemeAttrs = null; 462 } 463 464 onColorsChanged(); 465 } 466 467 /** 468 * Returns an appropriately themed color state list. 469 * 470 * @param t the theme to apply 471 * @return a copy of the color state list with the theme applied, or the 472 * color state list itself if there were no unresolved theme 473 * attributes 474 * @hide only for resource preloading 475 */ 476 @Override obtainForTheme(Theme t)477 public ColorStateList obtainForTheme(Theme t) { 478 if (t == null || !canApplyTheme()) { 479 return this; 480 } 481 482 final ColorStateList clone = new ColorStateList(this); 483 clone.applyTheme(t); 484 return clone; 485 } 486 487 /** 488 * Returns a mask of the configuration parameters for which this color 489 * state list may change, requiring that it be re-created. 490 * 491 * @return a mask of the changing configuration parameters, as defined by 492 * {@link android.content.pm.ActivityInfo} 493 * 494 * @see android.content.pm.ActivityInfo 495 */ getChangingConfigurations()496 public @Config int getChangingConfigurations() { 497 return super.getChangingConfigurations() | mChangingConfigurations; 498 } 499 modulateColorAlpha(int baseColor, float alphaMod)500 private int modulateColorAlpha(int baseColor, float alphaMod) { 501 if (alphaMod == 1.0f) { 502 return baseColor; 503 } 504 505 final int baseAlpha = Color.alpha(baseColor); 506 final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255); 507 return (baseColor & 0xFFFFFF) | (alpha << 24); 508 } 509 510 /** 511 * Indicates whether this color state list contains more than one state spec 512 * and will change color based on state. 513 * 514 * @return True if this color state list changes color based on state, false 515 * otherwise. 516 * @see #getColorForState(int[], int) 517 */ 518 @Override isStateful()519 public boolean isStateful() { 520 return mStateSpecs.length > 1; 521 } 522 523 /** 524 * Indicates whether this color state list is opaque, which means that every 525 * color returned from {@link #getColorForState(int[], int)} has an alpha 526 * value of 255. 527 * 528 * @return True if this color state list is opaque. 529 */ isOpaque()530 public boolean isOpaque() { 531 return mIsOpaque; 532 } 533 534 /** 535 * Return the color associated with the given set of 536 * {@link android.view.View} states. 537 * 538 * @param stateSet an array of {@link android.view.View} states 539 * @param defaultColor the color to return if there's no matching state 540 * spec in this {@link ColorStateList} that matches the 541 * stateSet. 542 * 543 * @return the color associated with that set of states in this {@link ColorStateList}. 544 */ getColorForState(@ullable int[] stateSet, int defaultColor)545 public int getColorForState(@Nullable int[] stateSet, int defaultColor) { 546 final int setLength = mStateSpecs.length; 547 for (int i = 0; i < setLength; i++) { 548 final int[] stateSpec = mStateSpecs[i]; 549 if (StateSet.stateSetMatches(stateSpec, stateSet)) { 550 return mColors[i]; 551 } 552 } 553 return defaultColor; 554 } 555 556 /** 557 * Return the default color in this {@link ColorStateList}. 558 * 559 * @return the default color in this {@link ColorStateList}. 560 */ 561 @ColorInt getDefaultColor()562 public int getDefaultColor() { 563 return mDefaultColor; 564 } 565 566 /** 567 * Return the states in this {@link ColorStateList}. The returned array 568 * should not be modified. 569 * 570 * @return the states in this {@link ColorStateList} 571 * @hide 572 */ getStates()573 public int[][] getStates() { 574 return mStateSpecs; 575 } 576 577 /** 578 * Return the colors in this {@link ColorStateList}. The returned array 579 * should not be modified. 580 * 581 * @return the colors in this {@link ColorStateList} 582 * @hide 583 */ getColors()584 public int[] getColors() { 585 return mColors; 586 } 587 588 /** 589 * Returns whether the specified state is referenced in any of the state 590 * specs contained within this ColorStateList. 591 * <p> 592 * Any reference, either positive or negative {ex. ~R.attr.state_enabled}, 593 * will cause this method to return {@code true}. Wildcards are not counted 594 * as references. 595 * 596 * @param state the state to search for 597 * @return {@code true} if the state if referenced, {@code false} otherwise 598 * @hide Use only as directed. For internal use only. 599 */ hasState(int state)600 public boolean hasState(int state) { 601 final int[][] stateSpecs = mStateSpecs; 602 final int specCount = stateSpecs.length; 603 for (int specIndex = 0; specIndex < specCount; specIndex++) { 604 final int[] states = stateSpecs[specIndex]; 605 final int stateCount = states.length; 606 for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) { 607 if (states[stateIndex] == state || states[stateIndex] == ~state) { 608 return true; 609 } 610 } 611 } 612 return false; 613 } 614 615 @Override toString()616 public String toString() { 617 return "ColorStateList{" + 618 "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) + 619 "mChangingConfigurations=" + mChangingConfigurations + 620 "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + 621 "mColors=" + Arrays.toString(mColors) + 622 "mDefaultColor=" + mDefaultColor + '}'; 623 } 624 625 /** 626 * Updates the default color and opacity. 627 */ onColorsChanged()628 private void onColorsChanged() { 629 int defaultColor = DEFAULT_COLOR; 630 boolean isOpaque = true; 631 632 final int[][] states = mStateSpecs; 633 final int[] colors = mColors; 634 final int N = states.length; 635 if (N > 0) { 636 defaultColor = colors[0]; 637 638 for (int i = N - 1; i > 0; i--) { 639 if (states[i].length == 0) { 640 defaultColor = colors[i]; 641 break; 642 } 643 } 644 645 for (int i = 0; i < N; i++) { 646 if (Color.alpha(colors[i]) != 0xFF) { 647 isOpaque = false; 648 break; 649 } 650 } 651 } 652 653 mDefaultColor = defaultColor; 654 mIsOpaque = isOpaque; 655 } 656 657 /** 658 * @return a factory that can create new instances of this ColorStateList 659 * @hide only for resource preloading 660 */ getConstantState()661 public ConstantState<ComplexColor> getConstantState() { 662 if (mFactory == null) { 663 mFactory = new ColorStateListFactory(this); 664 } 665 return mFactory; 666 } 667 668 private static class ColorStateListFactory extends ConstantState<ComplexColor> { 669 private final ColorStateList mSrc; 670 ColorStateListFactory(ColorStateList src)671 public ColorStateListFactory(ColorStateList src) { 672 mSrc = src; 673 } 674 675 @Override getChangingConfigurations()676 public @Config int getChangingConfigurations() { 677 return mSrc.mChangingConfigurations; 678 } 679 680 @Override newInstance()681 public ColorStateList newInstance() { 682 return mSrc; 683 } 684 685 @Override newInstance(Resources res, Theme theme)686 public ColorStateList newInstance(Resources res, Theme theme) { 687 return (ColorStateList) mSrc.obtainForTheme(theme); 688 } 689 } 690 691 @Override describeContents()692 public int describeContents() { 693 return 0; 694 } 695 696 @Override writeToParcel(Parcel dest, int flags)697 public void writeToParcel(Parcel dest, int flags) { 698 if (canApplyTheme()) { 699 Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!"); 700 } 701 final int N = mStateSpecs.length; 702 dest.writeInt(N); 703 for (int i = 0; i < N; i++) { 704 dest.writeIntArray(mStateSpecs[i]); 705 } 706 dest.writeIntArray(mColors); 707 } 708 709 public static final Parcelable.Creator<ColorStateList> CREATOR = 710 new Parcelable.Creator<ColorStateList>() { 711 @Override 712 public ColorStateList[] newArray(int size) { 713 return new ColorStateList[size]; 714 } 715 716 @Override 717 public ColorStateList createFromParcel(Parcel source) { 718 final int N = source.readInt(); 719 final int[][] stateSpecs = new int[N][]; 720 for (int i = 0; i < N; i++) { 721 stateSpecs[i] = source.createIntArray(); 722 } 723 final int[] colors = source.createIntArray(); 724 return new ColorStateList(stateSpecs, colors); 725 } 726 }; 727 } 728