blob: ca7f704c8dd57aec33dc11042b41e1c533d747b9 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.support.v7.content.res;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.util.StateSet;
import android.util.Xml;
import java.io.IOException;
final class AppCompatColorStateListInflater {
private static final int DEFAULT_COLOR = Color.RED;
private AppCompatColorStateListInflater() {}
/**
* Creates a ColorStateList from an XML document using given a set of
* {@link Resources} and a {@link Theme}.
*
* @param r Resources against which the ColorStateList should be inflated.
* @param parser Parser for the XML document defining the ColorStateList.
* @param theme Optional theme to apply to the color state list, may be
* {@code null}.
* @return A new color state list.
*/
@NonNull
public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
@Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {
// Seek parser to start tag.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
return createFromXmlInner(r, parser, attrs, theme);
}
/**
* Create from inside an XML document. Called on a parser positioned at a
* tag in an XML document, tries to create a ColorStateList from that tag.
*
* @throws XmlPullParserException if the current tag is not <selector>
* @return A new color state list for the current tag.
*/
@NonNull
private static ColorStateList createFromXmlInner(@NonNull Resources r,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
@Nullable Resources.Theme theme)
throws XmlPullParserException, IOException {
final String name = parser.getName();
if (!name.equals("selector")) {
throw new XmlPullParserException(
parser.getPositionDescription() + ": invalid color state list tag " + name);
}
return inflate(r, parser, attrs, theme);
}
/**
* Fill in this object based on the contents of an XML "selector" element.
*/
private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
@NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
throws XmlPullParserException, IOException {
final int innerDepth = parser.getDepth() + 1;
int depth;
int type;
int defaultColor = DEFAULT_COLOR;
int[][] stateSpecList = new int[20][];
int[] colorList = new int[stateSpecList.length];
int listSize = 0;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG || depth > innerDepth
|| !parser.getName().equals("item")) {
continue;
}
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
Color.MAGENTA);
float alphaMod = 1.0f;
if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod);
} else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) {
alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod);
}
a.recycle();
// Parse all unrecognized attributes as state specifiers.
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] stateSpec = new int[numAttrs];
for (int i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha
&& stateResId != R.attr.alpha) {
// Unrecognized attribute, add to state set
stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
? stateResId : -stateResId;
}
}
stateSpec = StateSet.trimStateSet(stateSpec, j);
// Apply alpha modulation. If we couldn't resolve the color or
// alpha yet, the default values leave us enough information to
// modulate again during applyTheme().
final int color = modulateColorAlpha(baseColor, alphaMod);
if (listSize == 0 || stateSpec.length == 0) {
defaultColor = color;
}
colorList = GrowingArrayUtils.append(colorList, listSize, color);
stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
}
int[] colors = new int[listSize];
int[][] stateSpecs = new int[listSize][];
System.arraycopy(colorList, 0, colors, 0, listSize);
System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize);
return new ColorStateList(stateSpecs, colors);
}
private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
AttributeSet set, int[] attrs) {
return theme == null ? res.obtainAttributes(set, attrs)
: theme.obtainStyledAttributes(set, attrs, 0, 0);
}
private static int modulateColorAlpha(int color, float alphaMod) {
return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod));
}
}