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