/*
* Copyright (C) 2016 Bilibili
*
* 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 com.bilibili.magicasakura.utils;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.support.v4.graphics.ColorUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.lang.reflect.Field;
/**
* @author xyczero617@gmail.com
* @time 16/2/22
*/
public class GradientDrawableUtils extends DrawableUtils {
private static Field sPaddingField;
private static Field sStPaddingField;
private static Field sStGradientPositions;
private static Field sStGradientAngle;
@Override
protected Drawable inflateDrawable(Context context, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException {
GradientDrawable gradientDrawable = new GradientDrawable();
inflateGradientRootElement(context, attrs, gradientDrawable);
int type;
final int innerDepth = parser.getDepth() + 1;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth) {
continue;
}
String name = parser.getName();
if (name.equals("size")) {
final int width = getAttrDimensionPixelSize(context, attrs, android.R.attr.width);
final int height = getAttrDimensionPixelSize(context, attrs, android.R.attr.height);
gradientDrawable.setSize(width, height);
} else if (name.equals("gradient") && Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
final float centerX = getAttrFloatOrFraction(context, attrs, android.R.attr.centerX, 0.5f, 1.0f, 1.0f);
final float centerY = getAttrFloatOrFraction(context, attrs, android.R.attr.centerY, 0.5f, 1.0f, 1.0f);
gradientDrawable.setGradientCenter(centerX, centerY);
final boolean useLevel = getAttrBoolean(context, attrs, android.R.attr.useLevel, false);
gradientDrawable.setUseLevel(useLevel);
final int gradientType = getAttrInt(context, attrs, android.R.attr.type, 0);
gradientDrawable.setGradientType(gradientType);
final int startColor = getAttrColor(context, attrs, android.R.attr.startColor, Color.TRANSPARENT);
final int centerColor = getAttrColor(context, attrs, android.R.attr.centerColor, Color.TRANSPARENT);
final int endColor = getAttrColor(context, attrs, android.R.attr.endColor, Color.TRANSPARENT);
if (!getAttrHasValue(context, attrs, android.R.attr.centerColor)) {
gradientDrawable.setColors(new int[]{startColor, endColor});
} else {
gradientDrawable.setColors(new int[]{startColor, centerColor, endColor});
setStGradientPositions(gradientDrawable.getConstantState(), 0.0f, centerX != 0.5f ? centerX : centerY, 1f);
}
if (gradientType == GradientDrawable.LINEAR_GRADIENT) {
int angle = (int) getAttrFloat(context, attrs, android.R.attr.angle, 0.0f);
angle %= 360;
if (angle % 45 != 0) {
throw new XmlPullParserException("<gradient> tag requires"
+ "'angle' attribute to "
+ "be a multiple of 45");
}
setStGradientAngle(gradientDrawable.getConstantState(), angle);
switch (angle) {
case 0:
gradientDrawable.setOrientation(GradientDrawable.Orientation.LEFT_RIGHT);
break;
case 45:
gradientDrawable.setOrientation(GradientDrawable.Orientation.BL_TR);
break;
case 90:
gradientDrawable.setOrientation(GradientDrawable.Orientation.BOTTOM_TOP);
break;
case 135:
gradientDrawable.setOrientation(GradientDrawable.Orientation.BR_TL);
break;
case 180:
gradientDrawable.setOrientation(GradientDrawable.Orientation.RIGHT_LEFT);
break;
case 225:
gradientDrawable.setOrientation(GradientDrawable.Orientation.TR_BL);
break;
case 270:
gradientDrawable.setOrientation(GradientDrawable.Orientation.TOP_BOTTOM);
break;
case 315:
gradientDrawable.setOrientation(GradientDrawable.Orientation.TL_BR);
break;
}
} else {
setGradientRadius(context, attrs, gradientDrawable, gradientType);
}
} else if (name.equals("solid")) {
int color = getAttrColor(context, attrs, android.R.attr.color, Color.TRANSPARENT);
gradientDrawable.setColor(getAlphaColor(color, getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f)));
} else if (name.equals("stroke")) {
final float alphaMod = getAttrFloat(context, attrs, android.R.attr.alpha, 1.0f);
final int strokeColor = getAttrColor(context, attrs, android.R.attr.color, Color.TRANSPARENT);
final int strokeWidth = getAttrDimensionPixelSize(context, attrs, android.R.attr.width);
final float dashWidth = getAttrDimension(context, attrs, android.R.attr.dashWidth);
if (dashWidth != 0.0f) {
final float dashGap = getAttrDimension(context, attrs, android.R.attr.dashGap);
gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod), dashWidth, dashGap);
} else {
gradientDrawable.setStroke(strokeWidth, getAlphaColor(strokeColor, alphaMod));
}
} else if (name.equals("corners")) {
final int radius = getAttrDimensionPixelSize(context, attrs, android.R.attr.radius);
gradientDrawable.setCornerRadius(radius);
final int topLeftRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.topLeftRadius, radius);
final int topRightRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.topRightRadius, radius);
final int bottomLeftRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.bottomLeftRadius, radius);
final int bottomRightRadius = getAttrDimensionPixelSize(context, attrs, android.R.attr.bottomRightRadius, radius);
if (topLeftRadius != radius || topRightRadius != radius ||
bottomLeftRadius != radius || bottomRightRadius != radius) {
// The corner radii are specified in clockwise order (see Path.addRoundRect())
gradientDrawable.setCornerRadii(new float[]{
topLeftRadius, topLeftRadius,
topRightRadius, topRightRadius,
bottomRightRadius, bottomRightRadius,
bottomLeftRadius, bottomLeftRadius
});
}
} else if (name.equals("padding")) {
final int paddingLeft = getAttrDimensionPixelOffset(context, attrs, android.R.attr.left);
final int paddingTop = getAttrDimensionPixelOffset(context, attrs, android.R.attr.top);
final int paddingRight = getAttrDimensionPixelOffset(context, attrs, android.R.attr.right);
final int paddingBottom = getAttrDimensionPixelOffset(context, attrs, android.R.attr.bottom);
if (paddingLeft != 0 || paddingTop != 0 || paddingRight != 0 || paddingBottom != 0) {
final Rect pad = new Rect();
pad.set(paddingLeft, paddingTop, paddingRight, paddingBottom);
try {
if (sPaddingField == null) {
sPaddingField = GradientDrawable.class.getDeclaredField("mPadding");
sPaddingField.setAccessible(true);
}
sPaddingField.set(gradientDrawable, pad);
if (sStPaddingField == null) {
sStPaddingField = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mPadding");
sStPaddingField.setAccessible(true);
}
sStPaddingField.set(gradientDrawable.getConstantState(), pad);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} else {
Log.w("drawable", "Bad element under <shape>: " + name);
}
}
return gradientDrawable;
}
void inflateGradientRootElement(Context context, AttributeSet attrs, GradientDrawable gradientDrawable) {
int shape = getAttrInt(context, attrs, android.R.attr.shape, GradientDrawable.RECTANGLE);
gradientDrawable.setShape(shape);
boolean dither = getAttrBoolean(context, attrs, android.R.attr.dither, false);
gradientDrawable.setDither(dither);
}
void setGradientRadius(Context context, AttributeSet attrs, GradientDrawable drawable, int gradientType) throws XmlPullParserException {
TypedArray a = obtainAttributes(context.getResources(), context.getTheme(), attrs, new int[]{android.R.attr.gradientRadius});
TypedValue value = a.peekValue(0);
if (value != null) {
boolean radiusRel = value.type == TypedValue.TYPE_FRACTION;
drawable.setGradientRadius(radiusRel ? value.getFraction(1.0f, 1.0f) : value.getFloat());
} else if (gradientType == GradientDrawable.RADIAL_GRADIENT) {
throw new XmlPullParserException(
"<gradient> tag requires 'gradientRadius' "
+ "attribute with radial type");
}
a.recycle();
}
void setStGradientAngle(Drawable.ConstantState constantState, int angle) {
try {
if (sStGradientAngle == null) {
sStGradientAngle = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mAngle");
sStGradientAngle.setAccessible(true);
}
sStGradientAngle.set(constantState, angle);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
void setStGradientPositions(Drawable.ConstantState constantState, float... positions) {
try {
if (sStGradientPositions == null) {
sStGradientPositions = Class.forName("android.graphics.drawable.GradientDrawable$GradientState").getDeclaredField("mPositions");
sStGradientPositions.setAccessible(true);
}
sStGradientPositions.set(constantState, positions);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
float getAttrFloatOrFraction(Context context, AttributeSet attrs, int attr, float defaultValue, float base, float pbase) {
TypedArray a = obtainAttributes(context.getResources(), context.getTheme(), attrs, new int[]{attr});
TypedValue tv = a.peekValue(0);
float v = defaultValue;
if (tv != null) {
boolean isFraction = tv.type == TypedValue.TYPE_FRACTION;
v = isFraction ? tv.getFraction(base, pbase) : tv.getFloat();
}
a.recycle();
return v;
}
int getAlphaColor(int baseColor, float alpha) {
return alpha != 1.0f
? ColorUtils.setAlphaComponent(baseColor, Math.round(Color.alpha(baseColor) * alpha))
: baseColor;
}
}