Home | History | Annotate | Download | only in res
      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