package org.itsnat.droid.impl.xmlinflater.drawable.classtree;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.util.TypedValue;
import org.itsnat.droid.ItsNatDroidException;
import org.itsnat.droid.impl.dom.DOMAttr;
import org.itsnat.droid.impl.dom.drawable.DOMElemDrawable;
import org.itsnat.droid.impl.util.MapSmart;
import org.itsnat.droid.impl.util.MiscUtil;
import org.itsnat.droid.impl.util.NamespaceUtil;
import org.itsnat.droid.impl.xmlinflated.drawable.ElementDrawableChildBase;
import org.itsnat.droid.impl.xmlinflated.drawable.ElementDrawableChildRoot;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildCorners;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildGradient;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildPadding;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildSize;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildSolid;
import org.itsnat.droid.impl.xmlinflated.drawable.GradientDrawableChildStroke;
import org.itsnat.droid.impl.xmlinflater.FieldContainer;
import org.itsnat.droid.impl.xmlinflater.PercFloatImpl;
import org.itsnat.droid.impl.xmlinflater.XMLInflaterContext;
import org.itsnat.droid.impl.xmlinflater.XMLInflaterRegistry;
import org.itsnat.droid.impl.xmlinflater.drawable.AttrDrawableContext;
import org.itsnat.droid.impl.xmlinflater.drawable.ClassDescDrawableMgr;
import org.itsnat.droid.impl.xmlinflater.drawable.XMLInflaterDrawable;
import org.itsnat.droid.impl.xmlinflater.shared.attr.AttrDesc;
import java.util.ArrayList;
/**
* Created by jmarranz on 10/11/14.
*/
public class ClassDescGradientDrawable extends ClassDescElementDrawableBased<GradientDrawable>
{
public static final MapSmart<String,Integer> shapeValueMap = MapSmart.<String,Integer>create(4);
static
{
shapeValueMap.put("rectangle", 0);
shapeValueMap.put("oval", 1);
shapeValueMap.put("line", 2);
shapeValueMap.put("ring", 3);
}
// shape type
private static final int RECTANGLE = 0;
private static final int OVAL = 1;
private static final int LINE = 2;
private static final int RING = 3;
// Sólo LOLLIPOP y superiores
private static final int RADIUS_TYPE_PIXELS = 0;
private static final int RADIUS_TYPE_FRACTION = 1;
private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
// Fin LOLLIPOP y superiores
protected FieldContainer<Integer> innerRadiusField;
protected FieldContainer<Float> innerRadiusRatioField;
protected FieldContainer<Integer> thicknessField;
protected FieldContainer<Float> thicknessRatioField;
protected FieldContainer<Boolean> useLevelForShapeField;
protected FieldContainer<Drawable.ConstantState> gradientStateField;
protected FieldContainer<Integer> gradientRadiusTypeField; // LOLLIPOP y sup
protected FieldContainer<GradientDrawable.Orientation> orientationField;
protected FieldContainer<int[]> gradientColorsField;
protected FieldContainer<float[]> gradientPositionsField;
protected FieldContainer<Rect> gradientPaddingField;
protected FieldContainer<Rect> gradientPaddingField2;
public ClassDescGradientDrawable(ClassDescDrawableMgr classMgr, ClassDescElementDrawableBased<? super GradientDrawable> parent)
{
super(classMgr,"shape",parent);
this.gradientStateField = new FieldContainer<Drawable.ConstantState>(GradientDrawable.class, "mGradientState");
Class gradientStateClass = MiscUtil.resolveClass(GradientDrawable.class.getName() + "$GradientState");
this.innerRadiusField = new FieldContainer<Integer>(gradientStateClass, "mInnerRadius");
this.innerRadiusRatioField = new FieldContainer<Float>(gradientStateClass, "mInnerRadiusRatio");
this.thicknessField = new FieldContainer<Integer>(gradientStateClass, "mThickness");
this.thicknessRatioField = new FieldContainer<Float>(gradientStateClass, "mThicknessRatio");
this.useLevelForShapeField = new FieldContainer<Boolean>(gradientStateClass, "mUseLevelForShape");
if (Build.VERSION.SDK_INT >= MiscUtil.LOLLIPOP) // 5.0
{
gradientRadiusTypeField = new FieldContainer<Integer>(gradientStateClass, "mGradientRadiusType");
}
this.orientationField = new FieldContainer<GradientDrawable.Orientation>(gradientStateClass, "mOrientation");
if (Build.VERSION.SDK_INT >= MiscUtil.MARSHMALLOW) // 6.0
this.gradientColorsField = new FieldContainer<int[]>(gradientStateClass, "mGradientColors");
else
this.gradientColorsField = new FieldContainer<int[]>(gradientStateClass, "mColors");
this.gradientPositionsField = new FieldContainer<float[]>(gradientStateClass, "mPositions");
this.gradientPaddingField = new FieldContainer<Rect>(GradientDrawable.class, "mPadding");
this.gradientPaddingField2 = new FieldContainer<Rect>(gradientStateClass, "mPadding");
}
@Override
public ElementDrawableChildRoot createElementDrawableChildRoot(DOMElemDrawable rootElem, AttrDrawableContext attrCtx)
{
ElementDrawableChildRoot elementDrawableRoot = new ElementDrawableChildRoot();
GradientDrawable drawable = new GradientDrawable();
XMLInflaterContext xmlInflaterContext = attrCtx.getXMLInflaterContext();
XMLInflaterRegistry xmlInflaterRegistry = classMgr.getXMLInflaterRegistry();
Drawable.ConstantState gradientState = gradientStateField.get(drawable);
DOMAttr attrShape = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "shape");
int shape = attrShape != null ? AttrDesc.<Integer>parseSingleName(attrShape.getValue(), shapeValueMap) : RECTANGLE;
DOMAttr attrDither = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "dither");
boolean dither = attrDither != null ? xmlInflaterRegistry.getBoolean(attrDither.getResourceDesc(),xmlInflaterContext) : false;
if (shape == RING)
{
DOMAttr attrInnerRadius = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "innerRadius");
int innerRadius = attrInnerRadius != null ? xmlInflaterRegistry.getDimensionIntRound(attrInnerRadius.getResourceDesc(), xmlInflaterContext) : -1; // Hay que iniciar en -1 para que no se use
innerRadiusField.set(gradientState,innerRadius);
if (innerRadius == -1)
{
DOMAttr attrInnerRadiusRatio = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "innerRadiusRatio");
if (attrInnerRadiusRatio != null)
{
float innerRadiusRatio = xmlInflaterRegistry.getFloat(attrInnerRadiusRatio.getResourceDesc(), xmlInflaterContext);
innerRadiusRatioField.set(gradientState,innerRadiusRatio);
}
}
DOMAttr attrThickness = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "thickness");
int thickness = attrThickness != null ? xmlInflaterRegistry.getDimensionIntRound(attrThickness.getResourceDesc(), xmlInflaterContext) : -1; // Hay que iniciar en -1 para que no se use
thicknessField.set(gradientState,thickness);
if (thickness == -1)
{
DOMAttr attrThicknessRatio = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "thicknessRatio");
if (attrThicknessRatio != null)
{
float thicknessRatio = xmlInflaterRegistry.getFloat(attrThicknessRatio.getResourceDesc(),xmlInflaterContext);
thicknessRatioField.set(gradientState,thicknessRatio);
}
}
DOMAttr attrUseLevelForShape = rootElem.getDOMAttribute(NamespaceUtil.XMLNS_ANDROID, "useLevel");
boolean useLevelForShape = attrUseLevelForShape != null ? xmlInflaterRegistry.getBoolean(attrUseLevelForShape.getResourceDesc(), xmlInflaterContext) : true;
useLevelForShapeField.set(gradientState,useLevelForShape);
}
drawable.setShape(shape);
drawable.setDither(dither);
XMLInflaterDrawable xmlInflaterDrawable = attrCtx.getXMLInflaterDrawable();
xmlInflaterDrawable.processChildElements(rootElem, elementDrawableRoot,attrCtx);
ArrayList<ElementDrawableChildBase> childList = elementDrawableRoot.getElementDrawableChildList();
for(int i = 0; i < childList.size(); i++)
{
ElementDrawableChildBase child = childList.get(i);
if (child instanceof GradientDrawableChildCorners)
{
processCorners(drawable,(GradientDrawableChildCorners)child);
}
else if (child instanceof GradientDrawableChildGradient)
{
processGradient(drawable,(GradientDrawableChildGradient)child,gradientState);
}
else if (child instanceof GradientDrawableChildPadding)
{
processPadding(drawable,(GradientDrawableChildPadding)child, gradientState);
}
else if (child instanceof GradientDrawableChildSize)
{
processSize(drawable,(GradientDrawableChildSize)child);
}
else if (child instanceof GradientDrawableChildSolid)
{
processSolid(drawable,(GradientDrawableChildSolid)child);
}
else if (child instanceof GradientDrawableChildStroke)
{
processStroke(drawable, (GradientDrawableChildStroke)child);
}
}
elementDrawableRoot.setDrawable(drawable);
return elementDrawableRoot;
}
private void processCorners(GradientDrawable drawable,GradientDrawableChildCorners child)
{
Integer radiusObj = child.getRadius();
int radius = radiusObj != null ? radiusObj.intValue() : 0;
drawable.setCornerRadius(radius);
Integer topLeftRadiusObj = child.getTopLeftRadius();
int topLeftRadius = topLeftRadiusObj != null ? topLeftRadiusObj.intValue() : radius;
Integer topRightRadiusObj = child.getTopRightRadius();
int topRightRadius = topRightRadiusObj != null ? topRightRadiusObj.intValue() : radius;
Integer bottomLeftRadiusObj = child.getBottomLeftRadius();
int bottomLeftRadius = bottomLeftRadiusObj != null ? bottomLeftRadiusObj.intValue() : radius;
Integer bottomRightRadiusObj = child.getBottomRightRadius();
int bottomRightRadius = bottomRightRadiusObj != null ? bottomRightRadiusObj.intValue() : radius;
if (topLeftRadius != radius || topRightRadius != radius ||
bottomLeftRadius != radius || bottomRightRadius != radius)
{
drawable.setCornerRadii(new float[]{
topLeftRadius, topLeftRadius,
topRightRadius, topRightRadius,
bottomRightRadius, bottomRightRadius,
bottomLeftRadius, bottomLeftRadius
});
}
}
private void processGradient(GradientDrawable drawable, GradientDrawableChildGradient child, Object gradientState)
{
PercFloatImpl centerXObj = child.getCenterX();
PercFloatImpl centerYObj = child.getCenterY();
float centerX = centerXObj != null ? centerXObj.toFloatBasedOnDataType() : 0.5f;
float centerY = centerYObj != null ? centerYObj.toFloatBasedOnDataType() : 0.5f;
drawable.setGradientCenter(centerX, centerY);
Integer startColorObj = child.getStartColor();
Integer centerColorObj = child.getCenterColor();
Integer endColorObj = child.getEndColor();
if (centerColorObj != null) // hasCenterColor
{
int[] colors = new int[3];
colors[0] = startColorObj != null ? startColorObj.intValue() : 0;
colors[1] = centerColorObj.intValue();
colors[2] = endColorObj != null ? endColorObj.intValue() : 0;
gradientColorsField.set(gradientState, colors);
// Necessary for correct center color position in rectangle case
float[] positions = new float[3];
positions[0] = 0.0f;
// Since 0.5f is default value, try to take the one that isn't 0.5f
positions[1] = centerX != 0.5f ? centerX : centerY;
positions[2] = 1f;
gradientPositionsField.set(gradientState, positions);
}
else
{
int[] colors = new int[2];
colors[0] = startColorObj != null ? startColorObj.intValue() : 0;
colors[1] = endColorObj != null ? endColorObj.intValue() : 0;
gradientColorsField.set(gradientState, colors);
}
Boolean useLevelObj = child.getUseLevel();
boolean useLevel = useLevelObj != null ? useLevelObj.booleanValue() : false;
drawable.setUseLevel(useLevel);
Integer gradientTypeObj = child.getType();
int gradientType = gradientTypeObj != null ? gradientTypeObj.intValue() : GradientDrawable.LINEAR_GRADIENT;
drawable.setGradientType(gradientType);
if (gradientType == GradientDrawable.LINEAR_GRADIENT)
{
int angle = 0;
Float angleFloat = child.getAngle();
if (angleFloat != null)
{
angle = (int) angleFloat.floatValue();
angle %= 360; // Deja el valor entre 0-360
if (angle % 45 != 0)
throw new ItsNatDroidException("<gradient> tag requires 'angle' attribute to be a multiple of 45");
GradientDrawable.Orientation orientation = GradientDrawable.Orientation.TOP_BOTTOM;
switch (angle)
{
case 0:
orientation = GradientDrawable.Orientation.LEFT_RIGHT;
break;
case 45:
orientation = GradientDrawable.Orientation.BL_TR;
break;
case 90:
orientation = GradientDrawable.Orientation.BOTTOM_TOP;
break;
case 135:
orientation = GradientDrawable.Orientation.BR_TL;
break;
case 180:
orientation = GradientDrawable.Orientation.RIGHT_LEFT;
break;
case 225:
orientation = GradientDrawable.Orientation.TR_BL;
break;
case 270:
orientation = GradientDrawable.Orientation.TOP_BOTTOM;
break;
case 315:
orientation = GradientDrawable.Orientation.TL_BR;
break;
}
orientationField.set(gradientState, orientation);
}
}
else // RADIAL_GRADIENT, SWEEP_GRADIENT
{
PercFloatImpl gradRadius = child.getGradientRadius();
if (gradRadius != null)
{
float value = gradRadius.toFloatBasedOnDataType(); // gradRadius.getValue();
drawable.setGradientRadius(value);
if (Build.VERSION.SDK_INT >= MiscUtil.LOLLIPOP)
{
int radiusType;
int dataType = gradRadius.getDataType();
if (dataType == TypedValue.TYPE_FRACTION)
{
radiusType = gradRadius.isFractionParent() ? RADIUS_TYPE_FRACTION_PARENT : RADIUS_TYPE_FRACTION;
}
else if (dataType == TypedValue.TYPE_FLOAT)
radiusType = RADIUS_TYPE_PIXELS;
else
throw MiscUtil.internalError();
gradientRadiusTypeField.set(gradientState,radiusType);
}
}
else if (gradientTypeObj == GradientDrawable.RADIAL_GRADIENT)
throw new ItsNatDroidException("<gradient> tag requires 'gradientRadius' attribute with radial type");
}
}
private void processPadding(GradientDrawable drawable, GradientDrawableChildPadding child, Object gradientState)
{
Integer leftObj = child.getLeft();
Integer topObj = child.getTop();
Integer rightObj = child.getRight();
Integer bottomObj = child.getBottom();
int left = leftObj != null ? leftObj.intValue() : 0;
int top = topObj != null ? topObj.intValue() : 0;
int right = rightObj != null ? rightObj.intValue() : 0;
int bottom = bottomObj != null ? bottomObj.intValue() : 0;
Rect rect = new Rect(left,top,right,bottom);
// Son necesarios los dos al menos el primero, de otra manera no se manifiesta visualmente el cambio
gradientPaddingField.set(drawable, rect);
gradientPaddingField2.set(gradientState, rect);
}
private void processSize(GradientDrawable drawable,GradientDrawableChildSize child)
{
Integer widthObj = child.getWidth();
Integer heightObj = child.getHeight();
int width = widthObj != null ? widthObj.intValue() : -1;
int height = heightObj != null ? heightObj.intValue() : -1;
drawable.setSize(width, height);
}
private void processSolid(GradientDrawable drawable,GradientDrawableChildSolid child)
{
Integer colorObj = child.getColor();
int color = colorObj != null ? colorObj.intValue() : 0;
drawable.setColor(color);
}
private void processStroke(GradientDrawable drawable,GradientDrawableChildStroke child)
{
Integer widthObj = child.getWidth();
Integer colorObj = child.getColor();
Float dashWidthObj = child.getDashWidth();
int width = widthObj != null ? widthObj.intValue() : 0;
int color = colorObj != null ? colorObj.intValue() : 0;
float dashWidth = dashWidthObj != null ? dashWidthObj.floatValue() : 0;
if (dashWidth != 0.0f)
{
Float dashGapObj = child.getDashGap();
float dashGap = dashGapObj != null ? dashGapObj.floatValue() : 0;
drawable.setStroke(width, color, dashWidthObj.floatValue(), dashGap);
}
else
{
drawable.setStroke(width, color);
}
}
@Override
public Class<GradientDrawable> getDrawableOrElementDrawableClass()
{
return GradientDrawable.class;
}
@Override
public boolean isAttributeIgnored(GradientDrawable resource, String namespaceURI, String name)
{
if (super.isAttributeIgnored(resource,namespaceURI,name))
return true;
return NamespaceUtil.XMLNS_ANDROID.equals(namespaceURI) &&
(name.equals("shape") || name.equals("dither") ||
name.equals("innerRadius") || name.equals("innerRadiusRatio") ||
name.equals("thickness") || name.equals("thicknessRatio") ||
name.equals("useLevel"));
}
protected void init()
{
super.init();
}
}