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