/* * Copyright (C) 2006 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 com.glview.graphics.drawable; import java.io.IOException; 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.graphics.PixelFormat; import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import com.glview.graphics.Path; import com.glview.graphics.Rect; import com.glview.graphics.RectF; import com.glview.graphics.shader.LinearGradient; import com.glview.graphics.shader.TileMode; import com.glview.hwui.GLCanvas; import com.glview.hwui.GLPaint; /** * A Drawable with a color gradient for buttons, backgrounds, etc. * * <p>It can be defined in an XML file with the <code><shape></code> element. For more * information, see the guide to <a * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> * * @attr ref android.R.styleable#GradientDrawable_visible * @attr ref android.R.styleable#GradientDrawable_shape * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio * @attr ref android.R.styleable#GradientDrawable_innerRadius * @attr ref android.R.styleable#GradientDrawable_thicknessRatio * @attr ref android.R.styleable#GradientDrawable_thickness * @attr ref android.R.styleable#GradientDrawable_useLevel * @attr ref android.R.styleable#GradientDrawableSize_width * @attr ref android.R.styleable#GradientDrawableSize_height * @attr ref android.R.styleable#GradientDrawableGradient_startColor * @attr ref android.R.styleable#GradientDrawableGradient_centerColor * @attr ref android.R.styleable#GradientDrawableGradient_endColor * @attr ref android.R.styleable#GradientDrawableGradient_useLevel * @attr ref android.R.styleable#GradientDrawableGradient_angle * @attr ref android.R.styleable#GradientDrawableGradient_type * @attr ref android.R.styleable#GradientDrawableGradient_centerX * @attr ref android.R.styleable#GradientDrawableGradient_centerY * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius * @attr ref android.R.styleable#GradientDrawableSolid_color * @attr ref android.R.styleable#GradientDrawableStroke_width * @attr ref android.R.styleable#GradientDrawableStroke_color * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth * @attr ref android.R.styleable#GradientDrawableStroke_dashGap * @attr ref android.R.styleable#GradientDrawablePadding_left * @attr ref android.R.styleable#GradientDrawablePadding_top * @attr ref android.R.styleable#GradientDrawablePadding_right * @attr ref android.R.styleable#GradientDrawablePadding_bottom */ public class GradientDrawable extends Drawable { /** * Shape is a rectangle, possibly with rounded corners */ public static final int RECTANGLE = 0; /** * Shape is an ellipse */ public static final int OVAL = 1; /** * Shape is a line */ public static final int LINE = 2; /** * Shape is a ring. */ public static final int RING = 3; /** * Gradient is linear (default.) */ public static final int LINEAR_GRADIENT = 0; /** * Gradient is circular. */ public static final int RADIAL_GRADIENT = 1; /** * Gradient is a sweep. */ public static final int SWEEP_GRADIENT = 2; /** Radius is in pixels. */ private static final int RADIUS_TYPE_PIXELS = 0; /** Radius is a fraction of the base size. */ private static final int RADIUS_TYPE_FRACTION = 1; /** Radius is a fraction of the bounds size. */ private static final int RADIUS_TYPE_FRACTION_PARENT = 2; private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; private static final float DEFAULT_THICKNESS_RATIO = 9.0f; private GradientState mGradientState; private final GLPaint mFillPaint = new GLPaint(); private Rect mPadding; private GLPaint mStrokePaint; // optional, set by the caller private int mAlpha = 0xFF; // modified by the caller private final Path mPath = new Path(); private final RectF mRect = new RectF(); private boolean mGradientIsDirty; // internal state private boolean mMutated; private Path mRingPath; private boolean mPathIsDirty = true; /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */ private float mGradientRadius; /** * Controls how the gradient is oriented relative to the drawable's bounds */ public enum Orientation { /** draw the gradient from the top to the bottom */ TOP_BOTTOM, /** draw the gradient from the top-right to the bottom-left */ TR_BL, /** draw the gradient from the right to the left */ RIGHT_LEFT, /** draw the gradient from the bottom-right to the top-left */ BR_TL, /** draw the gradient from the bottom to the top */ BOTTOM_TOP, /** draw the gradient from the bottom-left to the top-right */ BL_TR, /** draw the gradient from the left to the right */ LEFT_RIGHT, /** draw the gradient from the top-left to the bottom-right */ TL_BR, } public GradientDrawable() { this(new GradientState(Orientation.TOP_BOTTOM, null)); } /** * Create a new gradient drawable given an orientation and an array * of colors for the gradient. */ public GradientDrawable(Orientation orientation, int[] colors) { this(new GradientState(orientation, colors)); } @Override public boolean getPadding(Rect padding) { if (mPadding != null) { padding.set(mPadding); return true; } else { return super.getPadding(padding); } } /** * <p>Specify radii for each of the 4 corners. For each corner, the array * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered * top-left, top-right, bottom-right, bottom-left. This property * is honored only when the shape is of type {@link #RECTANGLE}.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param radii 4 pairs of X and Y radius for each corner, specified in pixels. * The length of this array must be >= 8 * * @see #mutate() * @see #setCornerRadii(float[]) * @see #setShape(int) */ public void setCornerRadii(float[] radii) { mGradientState.setCornerRadii(radii); mPathIsDirty = true; invalidateSelf(); } /** * <p>Specify radius for the corners of the gradient. If this is > 0, then the * drawable is drawn in a round-rectangle, rather than a rectangle. This property * is honored only when the shape is of type {@link #RECTANGLE}.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param radius The radius in pixels of the corners of the rectangle shape * * @see #mutate() * @see #setCornerRadii(float[]) * @see #setShape(int) */ public void setCornerRadius(float radius) { mGradientState.setCornerRadius(radius); mPathIsDirty = true; invalidateSelf(); } /** * <p>Set the stroke width and color for the drawable. If width is zero, * then no stroke is drawn.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param width The width in pixels of the stroke * @param color The color of the stroke * * @see #mutate() * @see #setStroke(int, int, float, float) */ public void setStroke(int width, int color) { setStroke(width, color, 0, 0); } /** * <p>Set the stroke width and color state list for the drawable. If width * is zero, then no stroke is drawn.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param width The width in pixels of the stroke * @param colorStateList The color state list of the stroke * * @see #mutate() * @see #setStroke(int, ColorStateList, float, float) */ public void setStroke(int width, ColorStateList colorStateList) { setStroke(width, colorStateList, 0, 0); } /** * <p>Set the stroke width and color for the drawable. If width is zero, * then no stroke is drawn. This method can also be used to dash the stroke.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param width The width in pixels of the stroke * @param color The color of the stroke * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes * @param dashGap The gap in pixels between dashes * * @see #mutate() * @see #setStroke(int, int) */ public void setStroke(int width, int color, float dashWidth, float dashGap) { mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap); setStrokeInternal(width, color, dashWidth, dashGap); } /** * <p>Set the stroke width and color state list for the drawable. If width * is zero, then no stroke is drawn. This method can also be used to dash * the stroke.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param width The width in pixels of the stroke * @param colorStateList The color state list of the stroke * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes * @param dashGap The gap in pixels between dashes * * @see #mutate() * @see #setStroke(int, ColorStateList) */ public void setStroke( int width, ColorStateList colorStateList, float dashWidth, float dashGap) { mGradientState.setStroke(width, colorStateList, dashWidth, dashGap); final int color; if (colorStateList == null) { color = Color.TRANSPARENT; } else { final int[] stateSet = getState(); color = colorStateList.getColorForState(stateSet, 0); } setStrokeInternal(width, color, dashWidth, dashGap); } private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) { if (mStrokePaint == null) { mStrokePaint = new GLPaint(); mStrokePaint.setStyle(GLPaint.Style.STROKE); } // mStrokePaint.setStrokeWidth(width); mStrokePaint.setColor(color); /*DashPathEffect e = null; if (dashWidth > 0) { e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); } mStrokePaint.setPathEffect(e);*/ invalidateSelf(); } /** * <p>Sets the size of the shape drawn by this drawable.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param width The width of the shape used by this drawable * @param height The height of the shape used by this drawable * * @see #mutate() * @see #setGradientType(int) */ public void setSize(int width, int height) { mGradientState.setSize(width, height); mPathIsDirty = true; invalidateSelf(); } /** * <p>Sets the type of shape used to draw the gradient.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param shape The desired shape for this drawable: {@link #LINE}, * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING} * * @see #mutate() */ public void setShape(int shape) { mRingPath = null; mPathIsDirty = true; mGradientState.setShape(shape); invalidateSelf(); } /** * <p>Sets the type of gradient used by this drawable..</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT}, * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT} * * @see #mutate() */ public void setGradientType(int gradient) { mGradientState.setGradientType(gradient); mGradientIsDirty = true; invalidateSelf(); } /** * <p>Sets the center location of the gradient. The radius is honored only when * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param x The x coordinate of the gradient's center * @param y The y coordinate of the gradient's center * * @see #mutate() * @see #setGradientType(int) */ public void setGradientCenter(float x, float y) { mGradientState.setGradientCenter(x, y); mGradientIsDirty = true; invalidateSelf(); } /** * <p>Sets the radius of the gradient. The radius is honored only when the * gradient type is set to {@link #RADIAL_GRADIENT}.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param gradientRadius The radius of the gradient in pixels * * @see #mutate() * @see #setGradientType(int) */ public void setGradientRadius(float gradientRadius) { mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX); mGradientIsDirty = true; invalidateSelf(); } /** * Returns the radius of the gradient in pixels. The radius is valid only * when the gradient type is set to {@link #RADIAL_GRADIENT}. * * @return Radius in pixels. */ public float getGradientRadius() { if (mGradientState.mGradient != RADIAL_GRADIENT) { return 0; } ensureValidRect(); return mGradientRadius; } /** * <p>Sets whether or not this drawable will honor its <code>level</code> * property.</p> * <p><strong>Note</strong>: changing this property will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing this property.</p> * * @param useLevel True if this drawable should honor its level, false otherwise * * @see #mutate() * @see #setLevel(int) * @see #getLevel() */ public void setUseLevel(boolean useLevel) { mGradientState.mUseLevel = useLevel; mGradientIsDirty = true; invalidateSelf(); } private int modulateAlpha(int alpha) { int scale = mAlpha + (mAlpha >> 7); return alpha * scale >> 8; } /** * Returns the orientation of the gradient defined in this drawable. */ public Orientation getOrientation() { return mGradientState.mOrientation; } /** * <p>Changes the orientation of the gradient defined in this drawable.</p> * <p><strong>Note</strong>: changing orientation will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing the orientation.</p> * * @param orientation The desired orientation (angle) of the gradient * * @see #mutate() */ public void setOrientation(Orientation orientation) { mGradientState.mOrientation = orientation; mGradientIsDirty = true; invalidateSelf(); } /** * <p>Sets the colors used to draw the gradient. Each color is specified as an * ARGB integer and the array must contain at least 2 colors.</p> * <p><strong>Note</strong>: changing orientation will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing the orientation.</p> * * @param colors 2 or more ARGB colors * * @see #mutate() * @see #setColor(int) */ public void setColors(int[] colors) { mGradientState.setColors(colors); mGradientIsDirty = true; invalidateSelf(); } @Override public void draw(GLCanvas canvas) { if (!ensureValidRect()) { // nothing to draw return; } // remember the alpha values, in case we temporarily overwrite them // when we modulate them with mAlpha final int prevFillAlpha = mFillPaint.getAlpha(); final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; // compute the modulate alpha values final int currFillAlpha = modulateAlpha(prevFillAlpha); final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null && mStrokePaint.getStrokeWidth() > 0; final boolean haveFill = currFillAlpha > 0; final GradientState st = mGradientState; mFillPaint.setAlpha(currFillAlpha); if (st.mColorStateList == null) { mFillPaint.setColor(mAlpha << 24); } if (haveStroke) { mStrokePaint.setAlpha(currStrokeAlpha); } switch (st.mShape) { case RECTANGLE: if (st.mRadiusArray != null) { // buildPathIfDirty(); // canvas.drawPath(mPath, mFillPaint); // if (haveStroke) { // canvas.drawPath(mPath, mStrokePaint); // } } else if (st.mRadius > 0.0f) { // since the caller is only giving us 1 value, we will force // it to be square if the rect is too small in one dimension // to show it. If we did nothing, Skia would clamp the rad // independently along each axis, giving us a thin ellipse // if the rect were very wide but not very tall float rad = Math.min(st.mRadius, Math.min(mRect.width(), mRect.height()) * 0.5f); canvas.drawRoundRect(mRect, rad, rad, mFillPaint); if (haveStroke) { canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); } } else { if (mFillPaint.getColor() != 0 || mFillPaint.getShader() != null) { canvas.drawRect(mRect, mFillPaint); } if (haveStroke) { canvas.drawRect(mRect, mStrokePaint); } } break; case OVAL: canvas.drawOval(mRect, mFillPaint); if (haveStroke) { canvas.drawOval(mRect, mStrokePaint); } break; case LINE: { RectF r = mRect; float y = r.centerY(); if (haveStroke) { canvas.drawLine(r.left, y, r.right, y, mStrokePaint); } break; } case RING: // Path path = buildRing(st); // canvas.drawPath(path, mFillPaint); // if (haveStroke) { // canvas.drawPath(path, mStrokePaint); // } break; } mFillPaint.setAlpha(prevFillAlpha); if (haveStroke) { mStrokePaint.setAlpha(prevStrokeAlpha); } } private void buildPathIfDirty() { final GradientState st = mGradientState; if (mPathIsDirty) { ensureValidRect(); mPath.reset(); mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); mPathIsDirty = false; } } private Path buildRing(GradientState st) { if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; mPathIsDirty = false; float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; RectF bounds = new RectF(mRect); float x = bounds.width() / 2.0f; float y = bounds.height() / 2.0f; float thickness = st.mThickness != -1 ? st.mThickness : bounds.width() / st.mThicknessRatio; // inner radius float radius = st.mInnerRadius != -1 ? st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; RectF innerBounds = new RectF(bounds); innerBounds.inset(x - radius, y - radius); bounds = new RectF(innerBounds); bounds.inset(-thickness, -thickness); if (mRingPath == null) { mRingPath = new Path(); } else { mRingPath.reset(); } final Path ringPath = mRingPath; // arcTo treats the sweep angle mod 360, so check for that, since we // think 360 means draw the entire oval if (sweep < 360 && sweep > -360) { // ringPath.setFillType(Path.FillType.EVEN_ODD); // inner top ringPath.moveTo(x + radius, y); // outer top ringPath.lineTo(x + radius + thickness, y); // outer arc // ringPath.arcTo(bounds, 0.0f, sweep, false); // // inner arc // ringPath.arcTo(innerBounds, sweep, -sweep, false); ringPath.close(); } else { // add the entire ovals // ringPath.addOval(bounds, Path.Direction.CW); // ringPath.addOval(innerBounds, Path.Direction.CCW); } return ringPath; } /** * <p>Changes this drawable to use a single color instead of a gradient.</p> * <p><strong>Note</strong>: changing color will affect all instances * of a drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing the color.</p> * * @param argb The color used to fill the shape * * @see #mutate() * @see #setColors(int[]) */ public void setColor(int argb) { mGradientState.setColorStateList(ColorStateList.valueOf(argb)); mFillPaint.setColor(argb); invalidateSelf(); } /** * Changes this drawable to use a single color state list instead of a * gradient. Calling this method with a null argument will clear the color * and is equivalent to calling {@link #setColor(int)} with the argument * {@link Color#TRANSPARENT}. * <p> * <strong>Note</strong>: changing color will affect all instances of a * drawable loaded from a resource. It is recommended to invoke * {@link #mutate()} before changing the color.</p> * * @param colorStateList The color state list used to fill the shape * @see #mutate() */ public void setColor(ColorStateList colorStateList) { mGradientState.setColorStateList(colorStateList); final int color; if (colorStateList == null) { color = Color.TRANSPARENT; } else { final int[] stateSet = getState(); color = colorStateList.getColorForState(stateSet, 0); } mFillPaint.setColor(color); invalidateSelf(); } @Override protected boolean onStateChange(int[] stateSet) { boolean invalidateSelf = false; final GradientState s = mGradientState; final ColorStateList stateList = s.mColorStateList; if (stateList != null) { final int newColor = stateList.getColorForState(stateSet, 0); final int oldColor = mFillPaint.getColor(); if (oldColor != newColor) { mFillPaint.setColor(newColor); invalidateSelf = true; } } final GLPaint strokePaint = mStrokePaint; if (strokePaint != null) { final ColorStateList strokeStateList = s.mStrokeColorStateList; if (strokeStateList != null) { final int newStrokeColor = strokeStateList.getColorForState(stateSet, 0); final int oldStrokeColor = strokePaint.getColor(); if (oldStrokeColor != newStrokeColor) { strokePaint.setColor(newStrokeColor); invalidateSelf = true; } } } if (invalidateSelf) { invalidateSelf(); return true; } return false; } @Override public boolean isStateful() { final GradientState s = mGradientState; return super.isStateful() || (s.mColorStateList != null && s.mColorStateList.isStateful()) || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful()); } @Override public int getChangingConfigurations() { return super.getChangingConfigurations() | mGradientState.mChangingConfigurations; } @Override public void setAlpha(int alpha) { if (alpha != mAlpha) { mAlpha = alpha; invalidateSelf(); } } @Override public int getAlpha() { return mAlpha; } @Override public void setDither(boolean dither) { if (dither != mGradientState.mDither) { mGradientState.mDither = dither; invalidateSelf(); } } @Override public int getOpacity() { return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; } @Override protected void onBoundsChange(Rect r) { super.onBoundsChange(r); mRingPath = null; mPathIsDirty = true; mGradientIsDirty = true; } @Override protected boolean onLevelChange(int level) { super.onLevelChange(level); mGradientIsDirty = true; mPathIsDirty = true; invalidateSelf(); return true; } /** * This checks mGradientIsDirty, and if it is true, recomputes both our drawing * rectangle (mRect) and the gradient itself, since it depends on our * rectangle too. * @return true if the resulting rectangle is not empty, false otherwise */ private boolean ensureValidRect() { if (mGradientIsDirty) { mGradientIsDirty = false; Rect bounds = getBounds(); float inset = 0; if (mStrokePaint != null) { inset = mStrokePaint.getStrokeWidth() * 0.5f; } final GradientState st = mGradientState; mRect.set(bounds.left + inset, bounds.top + inset, bounds.right - inset, bounds.bottom - inset); final int[] colors = st.mColors; if (colors != null) { RectF r = mRect; float x0, x1, y0, y1; if (st.mGradient == LINEAR_GRADIENT) { final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; switch (st.mOrientation) { case TOP_BOTTOM: x0 = r.left; y0 = r.top; x1 = x0; y1 = level * r.bottom; break; case TR_BL: x0 = r.right; y0 = r.top; x1 = level * r.left; y1 = level * r.bottom; break; case RIGHT_LEFT: x0 = r.right; y0 = r.top; x1 = level * r.left; y1 = y0; break; case BR_TL: x0 = r.right; y0 = r.bottom; x1 = level * r.left; y1 = level * r.top; break; case BOTTOM_TOP: x0 = r.left; y0 = r.bottom; x1 = x0; y1 = level * r.top; break; case BL_TR: x0 = r.left; y0 = r.bottom; x1 = level * r.right; y1 = level * r.top; break; case LEFT_RIGHT: x0 = r.left; y0 = r.top; x1 = level * r.right; y1 = y0; break; default:/* TL_BR */ x0 = r.left; y0 = r.top; x1 = level * r.right; y1 = level * r.bottom; break; } mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, colors, st.mPositions, TileMode.CLAMP)); } else if (st.mGradient == RADIAL_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; float radius = st.mGradientRadius; if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { radius *= Math.min(st.mWidth, st.mHeight); } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) { radius *= Math.min(r.width(), r.height()); } if (st.mUseLevel) { radius *= getLevel() / 10000.0f; } mGradientRadius = radius; if (radius == 0) { // We can't have a shader with zero radius, so let's // have a very, very small radius. radius = 0.001f; } // mFillPaint.setShader(new RadialGradient( // x0, y0, radius, colors, null, TileMode.CLAMP)); } else if (st.mGradient == SWEEP_GRADIENT) { x0 = r.left + (r.right - r.left) * st.mCenterX; y0 = r.top + (r.bottom - r.top) * st.mCenterY; int[] tempColors = colors; float[] tempPositions = null; if (st.mUseLevel) { tempColors = st.mTempColors; final int length = colors.length; if (tempColors == null || tempColors.length != length + 1) { tempColors = st.mTempColors = new int[length + 1]; } System.arraycopy(colors, 0, tempColors, 0, length); tempColors[length] = colors[length - 1]; tempPositions = st.mTempPositions; final float fraction = 1.0f / (length - 1); if (tempPositions == null || tempPositions.length != length + 1) { tempPositions = st.mTempPositions = new float[length + 1]; } final float level = getLevel() / 10000.0f; for (int i = 0; i < length; i++) { tempPositions[i] = i * fraction * level; } tempPositions[length] = 1.0f; } // mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); } // If we don't have a solid color, the alpha channel must be // maxed out so that alpha modulation works correctly. if (st.mColorStateList == null) { mFillPaint.setColor(Color.BLACK); } } } return !mRect.isEmpty(); } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { final TypedArray a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawable); super.inflateWithAttributes(r, parser, a, com.glview.R.styleable.GradientDrawable_visible); updateStateFromTypedArray(a); a.recycle(); inflateChildElements(r, parser, attrs); mGradientState.computeOpacity(); } /** * Updates the constant state from the values in the typed array. */ private void updateStateFromTypedArray(TypedArray a) { final GradientState state = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { state.mChangingConfigurations |= a.getChangingConfigurations(); } state.mShape = a.getInt(com.glview.R.styleable.GradientDrawable_shape, state.mShape); state.mDither = a.getBoolean(com.glview.R.styleable.GradientDrawable_dither, state.mDither); if (state.mShape == RING) { state.mInnerRadius = a.getDimensionPixelSize( com.glview.R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); if (state.mInnerRadius == -1) { state.mInnerRadiusRatio = a.getFloat( com.glview.R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); } state.mThickness = a.getDimensionPixelSize( com.glview.R.styleable.GradientDrawable_thickness, state.mThickness); if (state.mThickness == -1) { state.mThicknessRatio = a.getFloat( com.glview.R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); } state.mUseLevelForShape = a.getBoolean( com.glview.R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); } } private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { TypedArray a; 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")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawableSize); updateGradientDrawableSize(a); a.recycle(); } else if (name.equals("gradient")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawableGradient); updateGradientDrawableGradient(r, a); a.recycle(); } else if (name.equals("solid")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawableSolid); updateGradientDrawableSolid(a); a.recycle(); } else if (name.equals("stroke")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawableStroke); updateGradientDrawableStroke(a); a.recycle(); } else if (name.equals("corners")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.DrawableCorners); updateDrawableCorners(a); a.recycle(); } else if (name.equals("padding")) { a = obtainAttributes(r, attrs, com.glview.R.styleable.GradientDrawablePadding); updateGradientDrawablePadding(a); a.recycle(); } else { Log.w("drawable", "Bad element under <shape>: " + name); } } } private void updateGradientDrawablePadding(TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } if (st.mPadding == null) { st.mPadding = new Rect(); } final Rect pad = st.mPadding; pad.set(a.getDimensionPixelOffset(com.glview.R.styleable.GradientDrawablePadding_left, pad.left), a.getDimensionPixelOffset(com.glview.R.styleable.GradientDrawablePadding_top, pad.top), a.getDimensionPixelOffset(com.glview.R.styleable.GradientDrawablePadding_right, pad.right), a.getDimensionPixelOffset(com.glview.R.styleable.GradientDrawablePadding_bottom, pad.bottom)); mPadding = pad; } private void updateDrawableCorners(TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } final int radius = a.getDimensionPixelSize( com.glview.R.styleable.DrawableCorners_radius, (int) st.mRadius); setCornerRadius(radius); // TODO: Update these to be themeable. final int topLeftRadius = a.getDimensionPixelSize( com.glview.R.styleable.DrawableCorners_topLeftRadius, radius); final int topRightRadius = a.getDimensionPixelSize( com.glview.R.styleable.DrawableCorners_topRightRadius, radius); final int bottomLeftRadius = a.getDimensionPixelSize( com.glview.R.styleable.DrawableCorners_bottomLeftRadius, radius); final int bottomRightRadius = a.getDimensionPixelSize( com.glview.R.styleable.DrawableCorners_bottomRightRadius, radius); if (topLeftRadius != radius || topRightRadius != radius || bottomLeftRadius != radius || bottomRightRadius != radius) { // The corner radii are specified in clockwise order (see Path.addRoundRect()) setCornerRadii(new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, bottomRightRadius, bottomRightRadius, bottomLeftRadius, bottomLeftRadius }); } } private void updateGradientDrawableStroke(TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } // We have an explicit stroke defined, so the default stroke width // must be at least 0 or the current stroke width. final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); final int width = a.getDimensionPixelSize( com.glview.R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); final float dashWidth = a.getDimension( com.glview.R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); ColorStateList colorStateList = a.getColorStateList( com.glview.R.styleable.GradientDrawableStroke_color); if (colorStateList == null) { colorStateList = st.mStrokeColorStateList; } if (dashWidth != 0.0f) { final float dashGap = a.getDimension( com.glview.R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); setStroke(width, colorStateList, dashWidth, dashGap); } else { setStroke(width, colorStateList); } } private void updateGradientDrawableSolid(TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } final ColorStateList colorStateList = a.getColorStateList( com.glview.R.styleable.GradientDrawableSolid_color); if (colorStateList != null) { setColor(colorStateList); } } private void updateGradientDrawableGradient(Resources r, TypedArray a) throws XmlPullParserException { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } st.mCenterX = getFloatOrFraction( a, com.glview.R.styleable.GradientDrawableGradient_centerX, st.mCenterX); st.mCenterY = getFloatOrFraction( a, com.glview.R.styleable.GradientDrawableGradient_centerY, st.mCenterY); st.mUseLevel = a.getBoolean( com.glview.R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); st.mGradient = a.getInt( com.glview.R.styleable.GradientDrawableGradient_type, st.mGradient); // TODO: Update these to be themeable. final int startColor = a.getColor( com.glview.R.styleable.GradientDrawableGradient_startColor, 0); final boolean hasCenterColor = a.hasValue( com.glview.R.styleable.GradientDrawableGradient_centerColor); final int centerColor = a.getColor( com.glview.R.styleable.GradientDrawableGradient_centerColor, 0); final int endColor = a.getColor( com.glview.R.styleable.GradientDrawableGradient_endColor, 0); if (hasCenterColor) { st.mColors = new int[3]; st.mColors[0] = startColor; st.mColors[1] = centerColor; st.mColors[2] = endColor; st.mPositions = new float[3]; st.mPositions[0] = 0.0f; // Since 0.5f is default value, try to take the one that isn't 0.5f st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; st.mPositions[2] = 1f; } else { st.mColors = new int[2]; st.mColors[0] = startColor; st.mColors[1] = endColor; } if (st.mGradient == LINEAR_GRADIENT) { int angle = (int) a.getFloat(com.glview.R.styleable.GradientDrawableGradient_angle, st.mAngle); angle %= 360; if (angle % 45 != 0) { throw new XmlPullParserException(a.getPositionDescription() + "<gradient> tag requires 'angle' attribute to " + "be a multiple of 45"); } st.mAngle = angle; switch (angle) { case 0: st.mOrientation = Orientation.LEFT_RIGHT; break; case 45: st.mOrientation = Orientation.BL_TR; break; case 90: st.mOrientation = Orientation.BOTTOM_TOP; break; case 135: st.mOrientation = Orientation.BR_TL; break; case 180: st.mOrientation = Orientation.RIGHT_LEFT; break; case 225: st.mOrientation = Orientation.TR_BL; break; case 270: st.mOrientation = Orientation.TOP_BOTTOM; break; case 315: st.mOrientation = Orientation.TL_BR; break; } } else { final TypedValue tv = a.peekValue(com.glview.R.styleable.GradientDrawableGradient_gradientRadius); if (tv != null) { final float radius; final int radiusType; if (tv.type == TypedValue.TYPE_FRACTION) { radius = tv.getFraction(1.0f, 1.0f); final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) & TypedValue.COMPLEX_UNIT_MASK; if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { radiusType = RADIUS_TYPE_FRACTION_PARENT; } else { radiusType = RADIUS_TYPE_FRACTION; } } else { radius = tv.getDimension(r.getDisplayMetrics()); radiusType = RADIUS_TYPE_PIXELS; } st.mGradientRadius = radius; st.mGradientRadiusType = radiusType; } else if (st.mGradient == RADIAL_GRADIENT) { throw new XmlPullParserException( a.getPositionDescription() + "<gradient> tag requires 'gradientRadius' " + "attribute with radial type"); } } } private void updateGradientDrawableSize(TypedArray a) { final GradientState st = mGradientState; // Account for any configuration changes. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { st.mChangingConfigurations |= a.getChangingConfigurations(); } st.mWidth = a.getDimensionPixelSize(com.glview.R.styleable.GradientDrawableSize_width, st.mWidth); st.mHeight = a.getDimensionPixelSize(com.glview.R.styleable.GradientDrawableSize_height, st.mHeight); } private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { TypedValue tv = a.peekValue(index); float v = defaultValue; if (tv != null) { boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); } return v; } @Override public int getIntrinsicWidth() { return mGradientState.mWidth; } @Override public int getIntrinsicHeight() { return mGradientState.mHeight; } @Override public ConstantState getConstantState() { mGradientState.mChangingConfigurations = getChangingConfigurations(); return mGradientState; } private boolean isOpaqueForState() { if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null && !isOpaque(mStrokePaint.getColor())) { return false; } if (!isOpaque(mFillPaint.getColor())) { return false; } return true; } @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mGradientState = new GradientState(mGradientState); initializeWithState(mGradientState); mMutated = true; } return this; } final static class GradientState extends ConstantState { public int mChangingConfigurations; public int mShape = RECTANGLE; public int mGradient = LINEAR_GRADIENT; public int mAngle = 0; public Orientation mOrientation; public ColorStateList mColorStateList; public ColorStateList mStrokeColorStateList; public int[] mColors; public int[] mTempColors; // no need to copy public float[] mTempPositions; // no need to copy public float[] mPositions; public int mStrokeWidth = -1; // if >= 0 use stroking. public float mStrokeDashWidth = 0.0f; public float mStrokeDashGap = 0.0f; public float mRadius = 0.0f; // use this if mRadiusArray is null public float[] mRadiusArray = null; public Rect mPadding = null; public int mWidth = -1; public int mHeight = -1; public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; public int mInnerRadius = -1; public int mThickness = -1; public boolean mDither = false; private float mCenterX = 0.5f; private float mCenterY = 0.5f; private float mGradientRadius = 0.5f; private int mGradientRadiusType = RADIUS_TYPE_PIXELS; private boolean mUseLevel; private boolean mUseLevelForShape; private boolean mOpaqueOverBounds; private boolean mOpaqueOverShape; GradientState(Orientation orientation, int[] colors) { mOrientation = orientation; setColors(colors); } public GradientState(GradientState state) { mChangingConfigurations = state.mChangingConfigurations; mShape = state.mShape; mGradient = state.mGradient; mAngle = state.mAngle; mOrientation = state.mOrientation; mColorStateList = state.mColorStateList; if (state.mColors != null) { mColors = state.mColors.clone(); } if (state.mPositions != null) { mPositions = state.mPositions.clone(); } mStrokeColorStateList = state.mStrokeColorStateList; mStrokeWidth = state.mStrokeWidth; mStrokeDashWidth = state.mStrokeDashWidth; mStrokeDashGap = state.mStrokeDashGap; mRadius = state.mRadius; if (state.mRadiusArray != null) { mRadiusArray = state.mRadiusArray.clone(); } if (state.mPadding != null) { mPadding = new Rect(state.mPadding); } mWidth = state.mWidth; mHeight = state.mHeight; mInnerRadiusRatio = state.mInnerRadiusRatio; mThicknessRatio = state.mThicknessRatio; mInnerRadius = state.mInnerRadius; mThickness = state.mThickness; mDither = state.mDither; mCenterX = state.mCenterX; mCenterY = state.mCenterY; mGradientRadius = state.mGradientRadius; mGradientRadiusType = state.mGradientRadiusType; mUseLevel = state.mUseLevel; mUseLevelForShape = state.mUseLevelForShape; mOpaqueOverBounds = state.mOpaqueOverBounds; mOpaqueOverShape = state.mOpaqueOverShape; } @Override public Drawable newDrawable() { return new GradientDrawable(this); } @Override public Drawable newDrawable(Resources res) { return new GradientDrawable(this); } @Override public int getChangingConfigurations() { return mChangingConfigurations; } public void setShape(int shape) { mShape = shape; computeOpacity(); } public void setGradientType(int gradient) { mGradient = gradient; } public void setGradientCenter(float x, float y) { mCenterX = x; mCenterY = y; } public void setColors(int[] colors) { mColors = colors; mColorStateList = null; computeOpacity(); } public void setColorStateList(ColorStateList colorStateList) { mColors = null; mColorStateList = colorStateList; computeOpacity(); } private void computeOpacity() { mOpaqueOverBounds = false; mOpaqueOverShape = false; if (mColors != null) { for (int i = 0; i < mColors.length; i++) { if (!isOpaque(mColors[i])) { return; } } } // An unfilled shape is not opaque over bounds or shape if (mColors == null && mColorStateList == null) { return; } // Colors are opaque, so opaqueOverShape=true, mOpaqueOverShape = true; // and opaqueOverBounds=true if shape fills bounds mOpaqueOverBounds = mShape == RECTANGLE && mRadius <= 0 && mRadiusArray == null; } public void setStroke( int width, ColorStateList colorStateList, float dashWidth, float dashGap) { mStrokeWidth = width; mStrokeColorStateList = colorStateList; mStrokeDashWidth = dashWidth; mStrokeDashGap = dashGap; computeOpacity(); } public void setCornerRadius(float radius) { if (radius < 0) { radius = 0; } mRadius = radius; mRadiusArray = null; } public void setCornerRadii(float[] radii) { mRadiusArray = radii; if (radii == null) { mRadius = 0; } } public void setSize(int width, int height) { mWidth = width; mHeight = height; } public void setGradientRadius(float gradientRadius, int type) { mGradientRadius = gradientRadius; mGradientRadiusType = type; } } static boolean isOpaque(int color) { return ((color >> 24) & 0xff) == 0xff; } /** * Creates a new themed GradientDrawable based on the specified constant state. * <p> * The resulting drawable is guaranteed to have a new constant state. * * @param state Constant state from which the drawable inherits * @param theme Theme to apply to the drawable */ private GradientDrawable(GradientState state) { mGradientState = state; initializeWithState(state); mGradientIsDirty = true; mMutated = false; } private void initializeWithState(GradientState state) { if (state.mColorStateList != null) { final int[] currentState = getState(); final int stateColor = state.mColorStateList.getColorForState(currentState, 0); mFillPaint.setColor(stateColor); } else if (state.mColors == null) { // If we don't have a solid color and we don't have a gradient, // the app is stroking the shape, set the color to the default // value of state.mSolidColor mFillPaint.setColor(0); } else { // Otherwise, make sure the fill alpha is maxed out. mFillPaint.setColor(Color.BLACK); } mPadding = state.mPadding; if (state.mStrokeWidth >= 0) { mStrokePaint = new GLPaint(); mStrokePaint.setStyle(GLPaint.Style.STROKE); // mStrokePaint.setStrokeWidth(state.mStrokeWidth); if (state.mStrokeColorStateList != null) { final int[] currentState = getState(); final int strokeStateColor = state.mStrokeColorStateList.getColorForState( currentState, 0); mStrokePaint.setColor(strokeStateColor); } /*if (state.mStrokeDashWidth != 0.0f) { final DashPathEffect e = new DashPathEffect( new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); mStrokePaint.setPathEffect(e); }*/ } } }