/*
* Copyright (C) 2018 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 androidx.core.content.res;
import static android.graphics.Color.TRANSPARENT;
import static android.graphics.drawable.GradientDrawable.LINEAR_GRADIENT;
import static android.graphics.drawable.GradientDrawable.RADIAL_GRADIENT;
import static android.graphics.drawable.GradientDrawable.SWEEP_GRADIENT;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.LinearGradient;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.SweepGradient;
import android.util.AttributeSet;
import android.util.Xml;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.core.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
/**
* @hide
*/
@RestrictTo(LIBRARY_GROUP_PREFIX)
final class GradientColorInflaterCompat {
@IntDef({TILE_MODE_CLAMP, TILE_MODE_REPEAT, TILE_MODE_MIRROR})
@Retention(RetentionPolicy.SOURCE)
private @interface GradientTileMode {
}
private static final int TILE_MODE_CLAMP = 0;
private static final int TILE_MODE_REPEAT = 1;
private static final int TILE_MODE_MIRROR = 2;
private GradientColorInflaterCompat() {
}
static Shader createFromXml(@NonNull Resources resources, @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(resources, parser, attrs, theme);
}
static Shader createFromXmlInner(@NonNull Resources resources,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
@Nullable Resources.Theme theme)
throws IOException, XmlPullParserException {
final String name = parser.getName();
if (!name.equals("gradient")) {
throw new XmlPullParserException(
parser.getPositionDescription() + ": invalid gradient color tag " + name);
}
final TypedArray a = TypedArrayUtils.obtainAttributes(resources, theme, attrs,
R.styleable.GradientColor);
final float startX = TypedArrayUtils.getNamedFloat(a, parser, "startX",
R.styleable.GradientColor_android_startX, 0f);
final float startY = TypedArrayUtils.getNamedFloat(a, parser, "startY",
R.styleable.GradientColor_android_startY, 0f);
final float endX = TypedArrayUtils.getNamedFloat(a, parser, "endX",
R.styleable.GradientColor_android_endX, 0f);
final float endY = TypedArrayUtils.getNamedFloat(a, parser, "endY",
R.styleable.GradientColor_android_endY, 0f);
final float centerX = TypedArrayUtils.getNamedFloat(a, parser, "centerX",
R.styleable.GradientColor_android_centerX, 0f);
final float centerY = TypedArrayUtils.getNamedFloat(a, parser, "centerY",
R.styleable.GradientColor_android_centerY, 0f);
final int type = TypedArrayUtils.getNamedInt(a, parser, "type",
R.styleable.GradientColor_android_type, LINEAR_GRADIENT);
final int startColor = TypedArrayUtils.getNamedColor(a, parser, "startColor",
R.styleable.GradientColor_android_startColor, TRANSPARENT);
final boolean hasCenterColor = TypedArrayUtils.hasAttribute(parser, "centerColor");
final int centerColor = TypedArrayUtils.getNamedColor(a, parser, "centerColor",
R.styleable.GradientColor_android_centerColor, TRANSPARENT);
final int endColor = TypedArrayUtils.getNamedColor(a, parser, "endColor",
R.styleable.GradientColor_android_endColor, TRANSPARENT);
final int tileMode = TypedArrayUtils.getNamedInt(a, parser, "tileMode",
R.styleable.GradientColor_android_tileMode, TILE_MODE_CLAMP);
final float gradientRadius = TypedArrayUtils.getNamedFloat(a, parser, "gradientRadius",
R.styleable.GradientColor_android_gradientRadius, 0f);
a.recycle();
ColorStops colorStops = inflateChildElements(resources, parser, attrs, theme);
colorStops = checkColors(colorStops, startColor, endColor, hasCenterColor, centerColor);
switch (type) {
case RADIAL_GRADIENT:
if (gradientRadius <= 0f) {
throw new XmlPullParserException(
"<gradient> tag requires 'gradientRadius' attribute with radial type");
}
return new RadialGradient(centerX, centerY, gradientRadius, colorStops.mColors,
colorStops.mOffsets, parseTileMode(tileMode));
case SWEEP_GRADIENT:
return new SweepGradient(centerX, centerY, colorStops.mColors,
colorStops.mOffsets);
case LINEAR_GRADIENT:
default:
return new LinearGradient(startX, startY, endX, endY, colorStops.mColors,
colorStops.mOffsets, parseTileMode(tileMode));
}
}
private static ColorStops inflateChildElements(@NonNull Resources resources,
@NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
@Nullable Resources.Theme theme)
throws XmlPullParserException, IOException {
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
List<Float> offsets = new ArrayList<>(20);
List<Integer> colors = new ArrayList<>(20);
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
final TypedArray a = TypedArrayUtils.obtainAttributes(resources, theme, attrs,
R.styleable.GradientColorItem);
final boolean hasColor = a.hasValue(R.styleable.GradientColorItem_android_color);
final boolean hasOffset = a.hasValue(R.styleable.GradientColorItem_android_offset);
if (!hasColor || !hasOffset) {
throw new XmlPullParserException(
parser.getPositionDescription()
+ ": <item> tag requires a 'color' attribute and a 'offset' "
+ "attribute!");
}
final int color = a.getColor(R.styleable.GradientColorItem_android_color, TRANSPARENT);
final float offset = a.getFloat(R.styleable.GradientColorItem_android_offset, 0f);
a.recycle();
colors.add(color);
offsets.add(offset);
}
if (colors.size() > 0) return new ColorStops(colors, offsets);
return null;
}
private static ColorStops checkColors(@Nullable ColorStops colorItems, @ColorInt int startColor,
@ColorInt int endColor, boolean hasCenterColor, @ColorInt int centerColor) {
// prefer child color items if any, otherwise use the start, (center), end colors
if (colorItems != null) {
return colorItems;
} else if (hasCenterColor) {
return new ColorStops(startColor, centerColor, endColor);
} else {
return new ColorStops(startColor, endColor);
}
}
private static Shader.TileMode parseTileMode(@GradientTileMode int tileMode) {
switch (tileMode) {
case TILE_MODE_REPEAT:
return Shader.TileMode.REPEAT;
case TILE_MODE_MIRROR:
return Shader.TileMode.MIRROR;
case TILE_MODE_CLAMP:
default:
return Shader.TileMode.CLAMP;
}
}
static final class ColorStops {
final int[] mColors;
final float[] mOffsets;
ColorStops(@NonNull List<Integer> colorsList, @NonNull List<Float> offsetsList) {
final int size = colorsList.size();
mColors = new int[size];
mOffsets = new float[size];
for (int i = 0; i < size; i++) {
mColors[i] = colorsList.get(i);
mOffsets[i] = offsetsList.get(i);
}
}
ColorStops(@ColorInt int startColor, @ColorInt int endColor) {
mColors = new int[]{startColor, endColor};
mOffsets = new float[]{0f, 1f};
}
ColorStops(@ColorInt int startColor, @ColorInt int centerColor, @ColorInt int endColor) {
mColors = new int[]{startColor, centerColor, endColor};
mOffsets = new float[]{0f, 0.5f, 1f};
}
}
}