blob: b5b097b33ef3817c374550319c84c047ec5988e5 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
Tor Norbye80756e32015-03-02 09:39:27 -080019import android.annotation.ColorInt;
Alan Viverette45c4bbb2015-01-05 14:59:19 -080020import android.annotation.NonNull;
21import android.annotation.Nullable;
Alan Viveretteac85f902016-03-11 15:15:51 -050022import android.content.pm.ActivityInfo.Config;
Alan Viverette45c4bbb2015-01-05 14:59:19 -080023import android.content.res.Resources.Theme;
Alan Viverettea211dd22013-11-04 13:46:29 -080024import android.graphics.Color;
Alan Viverettecc3f3382014-02-22 16:52:46 -080025
Alan Viverette45c4bbb2015-01-05 14:59:19 -080026import com.android.internal.R;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import com.android.internal.util.ArrayUtils;
Adam Lesinski776abc22014-03-07 11:30:59 -050028import com.android.internal.util.GrowingArrayUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
Mathew Inwood5c0d3542018-08-14 13:54:31 +010033import android.annotation.UnsupportedAppUsage;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.util.AttributeSet;
Alan Viverette45c4bbb2015-01-05 14:59:19 -080035import android.util.Log;
Alan Viverettecc3f3382014-02-22 16:52:46 -080036import android.util.MathUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.util.SparseArray;
38import android.util.StateSet;
39import android.util.Xml;
40import android.os.Parcel;
41import android.os.Parcelable;
42
43import java.io.IOException;
44import java.lang.ref.WeakReference;
45import java.util.Arrays;
46
47/**
48 *
49 * Lets you map {@link android.view.View} state sets to colors.
Alan Viverette582dcac2016-05-16 12:37:55 -040050 * <p>
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051 * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
Alan Viverette582dcac2016-05-16 12:37:55 -040052 * "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:
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 * <pre>
55 * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
Alan Viverette582dcac2016-05-16 12:37:55 -040056 * &lt;item android:state_focused="true"
57 * android:color="@color/sample_focused" /&gt;
58 * &lt;item android:state_pressed="true"
59 * android:state_enabled="false"
60 * android:color="@color/sample_disabled_pressed" /&gt;
61 * &lt;item android:state_enabled="false"
62 * android:color="@color/sample_disabled_not_pressed" /&gt;
63 * &lt;item android:color="@color/sample_default" /&gt;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 * &lt;/selector&gt;
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
Alan Viverette582dcac2016-05-16 12:37:55 -040069 * 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 * &lt;item android:state_focused="true"
82 * android:color="@color/sample_focused" /&gt;
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>
Jiaquan Hef3800722017-02-02 14:26:45 -080095 * If an item with no state spec is placed before other items, those items
Alan Viverette582dcac2016-05-16 12:37:55 -040096 * 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 * &lt;item android:state_enabled="false"
112 * android:color="?android:attr/colorAccent"
113 * android:alpha="0.5" /&gt;
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 */
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800126public class ColorStateList extends ComplexColor implements Parcelable {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800127 private static final String TAG = "ColorStateList";
Alan Viverettee0f95f32015-04-01 13:10:18 -0700128
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800129 private static final int DEFAULT_COLOR = Color.RED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800130 private static final int[][] EMPTY = new int[][] { new int[0] };
Alan Viverettee0f95f32015-04-01 13:10:18 -0700131
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. */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100136 @UnsupportedAppUsage
Alan Viverettee0f95f32015-04-01 13:10:18 -0700137 private ColorStateListFactory mFactory;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800138
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800139 private int[][] mThemeAttrs;
Alan Viveretteac85f902016-03-11 15:15:51 -0500140 private @Config int mChangingConfigurations;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800141
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100142 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800143 private int[][] mStateSpecs;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100144 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800145 private int[] mColors;
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100146 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800147 private int mDefaultColor;
148 private boolean mIsOpaque;
149
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100150 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800151 private ColorStateList() {
152 // Not publicly instantiable.
153 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154
155 /**
156 * Creates a ColorStateList that returns the specified mapping from
157 * states to colors.
158 */
Tor Norbye80756e32015-03-02 09:39:27 -0800159 public ColorStateList(int[][] states, @ColorInt int[] colors) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 mStateSpecs = states;
161 mColors = colors;
162
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800163 onColorsChanged();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 }
165
166 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800167 * @return A ColorStateList containing a single color.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800168 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800169 @NonNull
Tor Norbye80756e32015-03-02 09:39:27 -0800170 public static ColorStateList valueOf(@ColorInt int color) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800171 synchronized (sCache) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800172 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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800178
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800179 // Prune missing entry.
180 sCache.removeAt(index);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 }
182
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800183 // 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 });
Alan Viverettee0f95f32015-04-01 13:10:18 -0700192 sCache.put(color, new WeakReference<>(csl));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193 return csl;
194 }
195 }
196
197 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800198 * 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800205 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800206 private ColorStateList(ColorStateList orig) {
207 if (orig != null) {
Alan Viverettee0f95f32015-04-01 13:10:18 -0700208 mChangingConfigurations = orig.mChangingConfigurations;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800209 mStateSpecs = orig.mStateSpecs;
210 mDefaultColor = orig.mDefaultColor;
211 mIsOpaque = orig.mIsOpaque;
212
Alan Viverettee0f95f32015-04-01 13:10:18 -0700213 // Deep copy, these may change due to applyTheme().
214 mThemeAttrs = orig.mThemeAttrs.clone();
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800215 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
231 throws XmlPullParserException, IOException {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800232 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
246 public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
247 @Nullable Theme theme) throws XmlPullParserException, IOException {
Alan Viverette3b5c4272014-05-20 13:20:42 -0700248 final AttributeSet attrs = Xml.asAttributeSet(parser);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249
250 int type;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800251 while ((type = parser.next()) != XmlPullParser.START_TAG
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 && type != XmlPullParser.END_DOCUMENT) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800253 // Seek parser to start tag.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800254 }
255
256 if (type != XmlPullParser.START_TAG) {
257 throw new XmlPullParserException("No start tag found");
258 }
259
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800260 return createFromXmlInner(r, parser, attrs, theme);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
262
Alan Viverette3b5c4272014-05-20 13:20:42 -0700263 /**
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 &lt;selector>
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800268 * @return A new color state list for the current tag.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800269 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800270 @NonNull
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800271 static ColorStateList createFromXmlInner(@NonNull Resources r,
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800272 @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
273 throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800274 final String name = parser.getName();
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800275 if (!name.equals("selector")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800276 throw new XmlPullParserException(
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800277 parser.getPositionDescription() + ": invalid color state list tag " + name);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800278 }
279
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800280 final ColorStateList colorStateList = new ColorStateList();
281 colorStateList.inflate(r, parser, attrs, theme);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800282 return colorStateList;
283 }
284
285 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800286 * 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.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800291 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800292 @NonNull
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 public ColorStateList withAlpha(int alpha) {
Alan Viverette3b5c4272014-05-20 13:20:42 -0700294 final int[] colors = new int[mColors.length];
295 final int len = colors.length;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 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 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800306 private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
307 @NonNull AttributeSet attrs, @Nullable Theme theme)
Alan Viverettecc3f3382014-02-22 16:52:46 -0800308 throws XmlPullParserException, IOException {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800309 final int innerDepth = parser.getDepth()+1;
310 int depth;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800311 int type;
312
Alan Viveretteac85f902016-03-11 15:15:51 -0500313 @Config int changingConfigurations = 0;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800314 int defaultColor = DEFAULT_COLOR;
315
316 boolean hasUnresolvedAttrs = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800317
Adam Lesinski776abc22014-03-07 11:30:59 -0500318 int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800319 int[][] themeAttrsList = new int[stateSpecList.length][];
Adam Lesinski776abc22014-03-07 11:30:59 -0500320 int[] colorList = new int[stateSpecList.length];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800321 int listSize = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800322
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800323 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")) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 continue;
328 }
329
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800330 final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
331 R.styleable.ColorStateListItem);
332 final int[] themeAttrs = a.extractThemeAttrs();
Alan Viverettef6014402015-04-29 15:36:11 -0700333 final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800334 final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800336 changingConfigurations |= a.getChangingConfigurations();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800337
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800338 a.recycle();
339
340 // Parse all unrecognized attributes as state specifiers.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800341 int j = 0;
342 final int numAttrs = attrs.getAttributeCount();
343 int[] stateSpec = new int[numAttrs];
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800344 for (int i = 0; i < numAttrs; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800345 final int stateResId = attrs.getAttributeNameResource(i);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800346 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800354 }
355 }
356 stateSpec = StateSet.trimStateSet(stateSpec, j);
357
Alan Viverettef6014402015-04-29 15:36:11 -0700358 // 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().
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800361 final int color = modulateColorAlpha(baseColor, alphaMod);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 if (listSize == 0 || stateSpec.length == 0) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800363 defaultColor = color;
364 }
365
366 if (themeAttrs != null) {
367 hasUnresolvedAttrs = true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800368 }
Alan Viverettecc3f3382014-02-22 16:52:46 -0800369
Adam Lesinski776abc22014-03-07 11:30:59 -0500370 colorList = GrowingArrayUtils.append(colorList, listSize, color);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800371 themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
Adam Lesinski776abc22014-03-07 11:30:59 -0500372 stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 listSize++;
374 }
375
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800376 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386 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);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800390
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
Alan Viverettee0f95f32015-04-01 13:10:18 -0700400 * @hide only for resource preloading
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800401 */
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800402 @Override
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100403 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800404 public boolean canApplyTheme() {
405 return mThemeAttrs != null;
406 }
407
408 /**
409 * Applies a theme to this color state list.
Alan Viverettee0f95f32015-04-01 13:10:18 -0700410 * <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()}.
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800415 *
416 * @param t the theme to apply
417 */
Alan Viverettee0f95f32015-04-01 13:10:18 -0700418 private void applyTheme(Theme t) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800419 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);
Alan Viverettef6014402015-04-29 15:36:11 -0700431
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
Alan Viverette50ba3212015-06-01 10:45:49 -0700446 // 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
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800454 final int baseColor = a.getColor(
455 R.styleable.ColorStateListItem_color, mColors[i]);
456 final float alphaMod = a.getFloat(
Alan Viverettef6014402015-04-29 15:36:11 -0700457 R.styleable.ColorStateListItem_alpha, defaultAlphaMod);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800458 mColors[i] = modulateColorAlpha(baseColor, alphaMod);
Alan Viverettef6014402015-04-29 15:36:11 -0700459
460 // Account for any configuration changes.
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800461 mChangingConfigurations |= a.getChangingConfigurations();
462
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800463 a.recycle();
464 }
465 }
466
467 if (!hasUnresolvedAttrs) {
468 mThemeAttrs = null;
469 }
470
471 onColorsChanged();
472 }
473
Alan Viverettee0f95f32015-04-01 13:10:18 -0700474 /**
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 */
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800483 @Override
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100484 @UnsupportedAppUsage
Alan Viverettee0f95f32015-04-01 13:10:18 -0700485 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 */
Alan Viveretteac85f902016-03-11 15:15:51 -0500504 public @Config int getChangingConfigurations() {
Alan Viverette0b9295d2016-03-10 10:30:08 -0500505 return super.getChangingConfigurations() | mChangingConfigurations;
Alan Viverettee0f95f32015-04-01 13:10:18 -0700506 }
507
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800508 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);
Alan Viverettee0f95f32015-04-01 13:10:18 -0700515 return (baseColor & 0xFFFFFF) | (alpha << 24);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800516 }
517
Alan Viverette3b5c4272014-05-20 13:20:42 -0700518 /**
Jiaquan He766b9b22017-02-17 16:44:59 -0800519 * Indicates whether this color state list contains at least one state spec
520 * and the first spec is not empty (e.g. match-all).
Alan Viverette3b5c4272014-05-20 13:20:42 -0700521 *
522 * @return True if this color state list changes color based on state, false
523 * otherwise.
524 * @see #getColorForState(int[], int)
525 */
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800526 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527 public boolean isStateful() {
Jiaquan He766b9b22017-02-17 16:44:59 -0800528 return mStateSpecs.length >= 1 && mStateSpecs[0].length > 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 }
Alan Viverettea211dd22013-11-04 13:46:29 -0800530
Alan Viverette3b5c4272014-05-20 13:20:42 -0700531 /**
Jiaquan Hef3800722017-02-02 14:26:45 -0800532 * Return whether the state spec list has at least one item explicitly specifying
533 * {@link android.R.attr#state_focused}.
534 * @hide
535 */
536 public boolean hasFocusStateSpecified() {
537 return StateSet.containsAttribute(mStateSpecs, R.attr.state_focused);
538 }
539
540 /**
Alan Viverette3b5c4272014-05-20 13:20:42 -0700541 * 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 */
Alan Viverettea211dd22013-11-04 13:46:29 -0800547 public boolean isOpaque() {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800548 return mIsOpaque;
Alan Viverettea211dd22013-11-04 13:46:29 -0800549 }
550
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800552 * Return the color associated with the given set of
553 * {@link android.view.View} states.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800554 *
555 * @param stateSet an array of {@link android.view.View} states
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800556 * @param defaultColor the color to return if there's no matching state
557 * spec in this {@link ColorStateList} that matches the
558 * stateSet.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 *
560 * @return the color associated with that set of states in this {@link ColorStateList}.
561 */
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800562 public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563 final int setLength = mStateSpecs.length;
564 for (int i = 0; i < setLength; i++) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800565 final int[] stateSpec = mStateSpecs[i];
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800566 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 */
Tor Norbye80756e32015-03-02 09:39:27 -0800578 @ColorInt
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 public int getDefaultColor() {
580 return mDefaultColor;
581 }
582
Jorim Jaggi39fa59f2014-02-25 15:38:45 +0100583 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800584 * Return the states in this {@link ColorStateList}. The returned array
585 * should not be modified.
586 *
Jorim Jaggi39fa59f2014-02-25 15:38:45 +0100587 * @return the states in this {@link ColorStateList}
588 * @hide
589 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100590 @UnsupportedAppUsage
Jorim Jaggi39fa59f2014-02-25 15:38:45 +0100591 public int[][] getStates() {
592 return mStateSpecs;
593 }
594
595 /**
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800596 * Return the colors in this {@link ColorStateList}. The returned array
597 * should not be modified.
598 *
Jorim Jaggi39fa59f2014-02-25 15:38:45 +0100599 * @return the colors in this {@link ColorStateList}
600 * @hide
601 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100602 @UnsupportedAppUsage
Jorim Jaggi39fa59f2014-02-25 15:38:45 +0100603 public int[] getColors() {
604 return mColors;
605 }
606
Alan Viverette0ef59ac2015-03-23 13:13:25 -0700607 /**
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 */
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
Alan Viverettecc3f3382014-02-22 16:52:46 -0800634 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800635 public String toString() {
636 return "ColorStateList{" +
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800637 "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
638 "mChangingConfigurations=" + mChangingConfigurations +
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
640 "mColors=" + Arrays.toString(mColors) +
641 "mDefaultColor=" + mDefaultColor + '}';
642 }
643
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800644 /**
645 * Updates the default color and opacity.
646 */
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100647 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800648 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 /**
Alan Viverettee0f95f32015-04-01 13:10:18 -0700678 * @return a factory that can create new instances of this ColorStateList
679 * @hide only for resource preloading
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800680 */
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800681 public ConstantState<ComplexColor> getConstantState() {
John Reck4feb3262015-07-13 14:42:43 -0700682 if (mFactory == null) {
Alan Viverettee0f95f32015-04-01 13:10:18 -0700683 mFactory = new ColorStateListFactory(this);
684 }
685 return mFactory;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800686 }
687
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800688 private static class ColorStateListFactory extends ConstantState<ComplexColor> {
Alan Viverettee0f95f32015-04-01 13:10:18 -0700689 private final ColorStateList mSrc;
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800690
Mathew Inwood5c0d3542018-08-14 13:54:31 +0100691 @UnsupportedAppUsage
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800692 public ColorStateListFactory(ColorStateList src) {
693 mSrc = src;
694 }
695
696 @Override
Alan Viveretteac85f902016-03-11 15:15:51 -0500697 public @Config int getChangingConfigurations() {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800698 return mSrc.mChangingConfigurations;
699 }
700
701 @Override
702 public ColorStateList newInstance() {
703 return mSrc;
704 }
705
706 @Override
707 public ColorStateList newInstance(Resources res, Theme theme) {
Teng-Hui Zhudbee9bb2015-12-15 11:01:27 -0800708 return (ColorStateList) mSrc.obtainForTheme(theme);
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800709 }
710 }
711
Alan Viverettecc3f3382014-02-22 16:52:46 -0800712 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 public int describeContents() {
714 return 0;
715 }
716
Alan Viverettecc3f3382014-02-22 16:52:46 -0800717 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800718 public void writeToParcel(Parcel dest, int flags) {
Alan Viverette45c4bbb2015-01-05 14:59:19 -0800719 if (canApplyTheme()) {
720 Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
721 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 final int N = mStateSpecs.length;
723 dest.writeInt(N);
Alan Viverettecc3f3382014-02-22 16:52:46 -0800724 for (int i = 0; i < N; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800725 dest.writeIntArray(mStateSpecs[i]);
726 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800727 dest.writeIntArray(mColors);
728 }
729
Jeff Sharkey9e8f83d2019-02-28 12:06:45 -0700730 public static final @android.annotation.NonNull Parcelable.Creator<ColorStateList> CREATOR =
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 new Parcelable.Creator<ColorStateList>() {
Alan Viverettecc3f3382014-02-22 16:52:46 -0800732 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800733 public ColorStateList[] newArray(int size) {
734 return new ColorStateList[size];
735 }
736
Alan Viverettecc3f3382014-02-22 16:52:46 -0800737 @Override
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800738 public ColorStateList createFromParcel(Parcel source) {
739 final int N = source.readInt();
Alan Viverettecc3f3382014-02-22 16:52:46 -0800740 final int[][] stateSpecs = new int[N][];
741 for (int i = 0; i < N; i++) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800742 stateSpecs[i] = source.createIntArray();
743 }
Alan Viverettecc3f3382014-02-22 16:52:46 -0800744 final int[] colors = source.createIntArray();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745 return new ColorStateList(stateSpecs, colors);
746 }
747 };
748}