1 /* 2 * Copyright (C) 2015 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 androidx.appcompat.content.res; 18 19 import android.content.res.ColorStateList; 20 import android.content.res.Resources; 21 import android.content.res.TypedArray; 22 import android.graphics.Color; 23 import android.util.AttributeSet; 24 import android.util.StateSet; 25 import android.util.Xml; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.Nullable; 29 import androidx.appcompat.R; 30 import androidx.core.graphics.ColorUtils; 31 32 import org.xmlpull.v1.XmlPullParser; 33 import org.xmlpull.v1.XmlPullParserException; 34 35 import java.io.IOException; 36 37 final class AppCompatColorStateListInflater { 38 39 private static final int DEFAULT_COLOR = Color.RED; 40 41 private AppCompatColorStateListInflater() {} 42 43 /** 44 * Creates a ColorStateList from an XML document using given a set of 45 * {@link Resources} and a {@link Theme}. 46 * 47 * @param r Resources against which the ColorStateList should be inflated. 48 * @param parser Parser for the XML document defining the ColorStateList. 49 * @param theme Optional theme to apply to the color state list, may be 50 * {@code null}. 51 * @return A new color state list. 52 */ 53 @NonNull 54 public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser, 55 @Nullable Resources.Theme theme) throws XmlPullParserException, IOException { 56 final AttributeSet attrs = Xml.asAttributeSet(parser); 57 58 int type; 59 while ((type = parser.next()) != XmlPullParser.START_TAG 60 && type != XmlPullParser.END_DOCUMENT) { 61 // Seek parser to start tag. 62 } 63 64 if (type != XmlPullParser.START_TAG) { 65 throw new XmlPullParserException("No start tag found"); 66 } 67 68 return createFromXmlInner(r, parser, attrs, theme); 69 } 70 71 /** 72 * Create from inside an XML document. Called on a parser positioned at a 73 * tag in an XML document, tries to create a ColorStateList from that tag. 74 * 75 * @throws XmlPullParserException if the current tag is not <selector> 76 * @return A new color state list for the current tag. 77 */ 78 @NonNull 79 private static ColorStateList createFromXmlInner(@NonNull Resources r, 80 @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, 81 @Nullable Resources.Theme theme) 82 throws XmlPullParserException, IOException { 83 final String name = parser.getName(); 84 if (!name.equals("selector")) { 85 throw new XmlPullParserException( 86 parser.getPositionDescription() + ": invalid color state list tag " + name); 87 } 88 89 return inflate(r, parser, attrs, theme); 90 } 91 92 /** 93 * Fill in this object based on the contents of an XML "selector" element. 94 */ 95 private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 96 @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) 97 throws XmlPullParserException, IOException { 98 final int innerDepth = parser.getDepth() + 1; 99 int depth; 100 int type; 101 int defaultColor = DEFAULT_COLOR; 102 103 int[][] stateSpecList = new int[20][]; 104 int[] colorList = new int[stateSpecList.length]; 105 int listSize = 0; 106 107 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 108 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 109 if (type != XmlPullParser.START_TAG || depth > innerDepth 110 || !parser.getName().equals("item")) { 111 continue; 112 } 113 114 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem); 115 final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color, 116 Color.MAGENTA); 117 118 float alphaMod = 1.0f; 119 if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) { 120 alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod); 121 } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) { 122 alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod); 123 } 124 125 a.recycle(); 126 127 // Parse all unrecognized attributes as state specifiers. 128 int j = 0; 129 final int numAttrs = attrs.getAttributeCount(); 130 int[] stateSpec = new int[numAttrs]; 131 for (int i = 0; i < numAttrs; i++) { 132 final int stateResId = attrs.getAttributeNameResource(i); 133 if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha 134 && stateResId != R.attr.alpha) { 135 // Unrecognized attribute, add to state set 136 stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) 137 ? stateResId : -stateResId; 138 } 139 } 140 stateSpec = StateSet.trimStateSet(stateSpec, j); 141 142 // Apply alpha modulation. If we couldn't resolve the color or 143 // alpha yet, the default values leave us enough information to 144 // modulate again during applyTheme(). 145 final int color = modulateColorAlpha(baseColor, alphaMod); 146 if (listSize == 0 || stateSpec.length == 0) { 147 defaultColor = color; 148 } 149 150 colorList = GrowingArrayUtils.append(colorList, listSize, color); 151 stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); 152 listSize++; 153 } 154 155 int[] colors = new int[listSize]; 156 int[][] stateSpecs = new int[listSize][]; 157 System.arraycopy(colorList, 0, colors, 0, listSize); 158 System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize); 159 160 return new ColorStateList(stateSpecs, colors); 161 } 162 163 private static TypedArray obtainAttributes(Resources res, Resources.Theme theme, 164 AttributeSet set, int[] attrs) { 165 return theme == null ? res.obtainAttributes(set, attrs) 166 : theme.obtainStyledAttributes(set, attrs, 0, 0); 167 } 168 169 private static int modulateColorAlpha(int color, float alphaMod) { 170 return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod)); 171 } 172 } 173