19d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes/* 29d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Copyright (C) 2015 The Android Open Source Project 39d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * 49d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 59d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * you may not use this file except in compliance with the License. 69d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * You may obtain a copy of the License at 79d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * 89d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * http://www.apache.org/licenses/LICENSE-2.0 99d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * 109d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Unless required by applicable law or agreed to in writing, software 119d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 129d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 139d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * See the License for the specific language governing permissions and 149d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * limitations under the License. 159d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes */ 169d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1715aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banespackage android.support.v7.content.res; 189d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 199d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport org.xmlpull.v1.XmlPullParser; 209d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport org.xmlpull.v1.XmlPullParserException; 219d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 229d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.ColorStateList; 239d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.Resources; 249d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.content.res.TypedArray; 259d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.graphics.Color; 269d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.annotation.NonNull; 279d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.annotation.Nullable; 289d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.v4.graphics.ColorUtils; 299d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.support.v7.appcompat.R; 309d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.AttributeSet; 319d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.StateSet; 329d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport android.util.Xml; 339d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 349d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banesimport java.io.IOException; 359d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 3615aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banesfinal class AppCompatColorStateListInflater { 379d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 389d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes private static final int DEFAULT_COLOR = Color.RED; 399d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 4015aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes private AppCompatColorStateListInflater() {} 4115aeaf26caa61ed5f3cd367044801d03c1a0a2b5Chris Banes 429d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes /** 439d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Creates a ColorStateList from an XML document using given a set of 449d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * {@link Resources} and a {@link Theme}. 459d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * 469d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @param r Resources against which the ColorStateList should be inflated. 479d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @param parser Parser for the XML document defining the ColorStateList. 489d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @param theme Optional theme to apply to the color state list, may be 499d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * {@code null}. 509d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @return A new color state list. 519d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes */ 529d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @NonNull 539d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser, 549d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @Nullable Resources.Theme theme) throws XmlPullParserException, IOException { 559d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final AttributeSet attrs = Xml.asAttributeSet(parser); 569d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 579d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int type; 589d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes while ((type = parser.next()) != XmlPullParser.START_TAG 599d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes && type != XmlPullParser.END_DOCUMENT) { 609d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // Seek parser to start tag. 619d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 629d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 639d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (type != XmlPullParser.START_TAG) { 649d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes throw new XmlPullParserException("No start tag found"); 659d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 669d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 679d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes return createFromXmlInner(r, parser, attrs, theme); 689d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 699d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 709d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes /** 719d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Create from inside an XML document. Called on a parser positioned at a 729d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * tag in an XML document, tries to create a ColorStateList from that tag. 739d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * 749d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @throws XmlPullParserException if the current tag is not <selector> 759d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * @return A new color state list for the current tag. 769d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes */ 779d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @NonNull 789d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes private static ColorStateList createFromXmlInner(@NonNull Resources r, 799d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, 809d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @Nullable Resources.Theme theme) 819d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes throws XmlPullParserException, IOException { 829d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final String name = parser.getName(); 839d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (!name.equals("selector")) { 849d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes throw new XmlPullParserException( 859d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes parser.getPositionDescription() + ": invalid color state list tag " + name); 869d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 879d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 889d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes return inflate(r, parser, attrs, theme); 899d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 909d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 919d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes /** 929d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes * Fill in this object based on the contents of an XML "selector" element. 939d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes */ 949d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 959d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) 969d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes throws XmlPullParserException, IOException { 979d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final int innerDepth = parser.getDepth() + 1; 989d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int depth; 999d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int type; 1009d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int defaultColor = DEFAULT_COLOR; 1019d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1029d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int[][] stateSpecList = new int[20][]; 1039d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int[] colorList = new int[stateSpecList.length]; 1049d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int listSize = 0; 1059d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1069d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 1079d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 1089d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (type != XmlPullParser.START_TAG || depth > innerDepth 1099d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes || !parser.getName().equals("item")) { 1109d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes continue; 1119d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1129d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1139d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem); 1149d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color, 1159d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes Color.MAGENTA); 1169d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1179d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes float alphaMod = 1.0f; 1189d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) { 1199d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod); 1209d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) { 1219d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod); 1229d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1239d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1249d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes a.recycle(); 1259d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1269d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // Parse all unrecognized attributes as state specifiers. 1279d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int j = 0; 1289d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final int numAttrs = attrs.getAttributeCount(); 1299d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int[] stateSpec = new int[numAttrs]; 1309d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes for (int i = 0; i < numAttrs; i++) { 1319d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final int stateResId = attrs.getAttributeNameResource(i); 1329d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha 1339d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes && stateResId != R.attr.alpha) { 1349d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // Unrecognized attribute, add to state set 1359d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) 1369d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes ? stateResId : -stateResId; 1379d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1389d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1399d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes stateSpec = StateSet.trimStateSet(stateSpec, j); 1409d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1419d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // Apply alpha modulation. If we couldn't resolve the color or 1429d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // alpha yet, the default values leave us enough information to 1439d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes // modulate again during applyTheme(). 1449d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes final int color = modulateColorAlpha(baseColor, alphaMod); 1459d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes if (listSize == 0 || stateSpec.length == 0) { 1469d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes defaultColor = color; 1479d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1489d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1499d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes colorList = GrowingArrayUtils.append(colorList, listSize, color); 1509d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); 1519d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes listSize++; 1529d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1539d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1549d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int[] colors = new int[listSize]; 1559d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes int[][] stateSpecs = new int[listSize][]; 1569d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes System.arraycopy(colorList, 0, colors, 0, listSize); 1579d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize); 1589d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1599d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes return new ColorStateList(stateSpecs, colors); 1609d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1619d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1629d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes private static TypedArray obtainAttributes(Resources res, Resources.Theme theme, 1639d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes AttributeSet set, int[] attrs) { 1649d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes return theme == null ? res.obtainAttributes(set, attrs) 1659d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes : theme.obtainStyledAttributes(set, attrs, 0, 0); 1669d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1679d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes 1689d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes private static int modulateColorAlpha(int color, float alphaMod) { 1699d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod)); 1709d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes } 1719d5f84f33353a42e837c6b465412d1a6f2fc6eaaChris Banes} 172