/* * 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 android.graphics.drawable; import android.annotation.ColorInt; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo.Config; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.DashPathEffect; import android.graphics.Insets; import android.graphics.LinearGradient; import android.graphics.Outline; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.RadialGradient; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; import android.graphics.Xfermode; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import com.android.internal.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; /** * A Drawable with a color gradient for buttons, backgrounds, etc. * *

It can be defined in an XML file with the <shape> element. For more * information, see the guide to Drawable Resources.

* * @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; /** @hide */ @IntDef({RECTANGLE, OVAL, LINE, RING}) @Retention(RetentionPolicy.SOURCE) public @interface Shape {} /** * 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; /** @hide */ @IntDef({LINEAR_GRADIENT, RADIAL_GRADIENT, SWEEP_GRADIENT}) @Retention(RetentionPolicy.SOURCE) public @interface GradientType {} /** 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; /** @hide */ @IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT}) @Retention(RetentionPolicy.SOURCE) public @interface RadiusType {} private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; private static final float DEFAULT_THICKNESS_RATIO = 9.0f; private GradientState mGradientState; private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private Rect mPadding; private Paint mStrokePaint; // optional, set by the caller private ColorFilter mColorFilter; // optional, set by the caller private PorterDuffColorFilter mTintFilter; private int mAlpha = 0xFF; // modified by the caller private final Path mPath = new Path(); private final RectF mRect = new RectF(); private Paint mLayerPaint; // internal, used if we use saveLayer() private boolean mGradientIsDirty; 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), null); } /** * Create a new gradient drawable given an orientation and an array * of colors for the gradient. */ public GradientDrawable(Orientation orientation, @ColorInt int[] colors) { this(new GradientState(orientation, colors), null); } @Override public boolean getPadding(Rect padding) { if (mPadding != null) { padding.set(mPadding); return true; } else { return super.getPadding(padding); } } /** * Specifies radii for each of the 4 corners. For each corner, the array * contains 2 values, [X_radius, Y_radius]. 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}. *

* Note: 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. * * @param radii an array of length >= 8 containing 4 pairs of X and Y * radius for each corner, specified in pixels * * @see #mutate() * @see #setShape(int) * @see #setCornerRadius(float) */ public void setCornerRadii(@Nullable float[] radii) { mGradientState.setCornerRadii(radii); mPathIsDirty = true; invalidateSelf(); } /** * Returns the radii for each of the 4 corners. For each corner, the array * contains 2 values, [X_radius, Y_radius]. The corners are * ordered top-left, top-right, bottom-right, bottom-left. *

* If the radius was previously set with {@link #setCornerRadius(float)}, * or if the corners are not rounded, this method will return {@code null}. * * @return an array containing the radii for each of the 4 corners, or * {@code null} * @see #setCornerRadii(float[]) */ @Nullable public float[] getCornerRadii() { return mGradientState.mRadiusArray.clone(); } /** * Specifies the 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}. *

* Note: 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. * * @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(); } /** * Returns the radius for the corners of the gradient, that was previously set with * {@link #setCornerRadius(float)}. *

* If the radius was previously cleared via passing {@code null} * to {@link #setCornerRadii(float[])}, this method will return 0. * * @return the radius in pixels of the corners of the rectangle shape, or 0 * @see #setCornerRadius */ public float getCornerRadius() { return mGradientState.mRadius; } /** *

Set the stroke width and color for the drawable. If width is zero, * then no stroke is drawn.

*

Note: 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.

* * @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, @ColorInt int color) { setStroke(width, color, 0, 0); } /** *

Set the stroke width and color state list for the drawable. If width * is zero, then no stroke is drawn.

*

Note: 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.

* * @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); } /** *

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.

*

Note: 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.

* * @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, @ColorInt int color, float dashWidth, float dashGap) { mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap); setStrokeInternal(width, color, dashWidth, dashGap); } /** *

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.

*

Note: 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.

* * @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 Paint(Paint.ANTI_ALIAS_FLAG); mStrokePaint.setStyle(Paint.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(); } /** *

Sets the size of the shape drawn by this drawable.

*

Note: 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.

* * @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(); } /** *

Sets the type of shape used to draw the gradient.

*

Note: 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.

* * @param shape The desired shape for this drawable: {@link #LINE}, * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING} * * @see #mutate() */ public void setShape(@Shape int shape) { mRingPath = null; mPathIsDirty = true; mGradientState.setShape(shape); invalidateSelf(); } /** * Returns the type of shape used by this drawable, one of {@link #LINE}, * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}. * * @return the type of shape used by this drawable * @see #setShape(int) */ @Shape public int getShape() { return mGradientState.mShape; } /** * Sets the type of gradient used by this drawable. *

* Note: 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. * * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT}, * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT} * * @see #mutate() * @see #getGradientType() */ public void setGradientType(@GradientType int gradient) { mGradientState.setGradientType(gradient); mGradientIsDirty = true; invalidateSelf(); } /** * Returns the type of gradient used by this drawable, one of * {@link #LINEAR_GRADIENT}, {@link #RADIAL_GRADIENT}, or * {@link #SWEEP_GRADIENT}. * * @return the type of gradient used by this drawable * @see #setGradientType(int) */ @GradientType public int getGradientType() { return mGradientState.mGradient; } /** * Sets the position of the center of the gradient as a fraction of the * width and height. *

* The default value is (0.5, 0.5). *

* Note: 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. * * @param x the X-position of the center of the gradient * @param y the Y-position of the center of the gradient * * @see #mutate() * @see #setGradientType(int) * @see #getGradientCenterX() * @see #getGradientCenterY() */ public void setGradientCenter(float x, float y) { mGradientState.setGradientCenter(x, y); mGradientIsDirty = true; invalidateSelf(); } /** * Returns the X-position of the center of the gradient as a fraction of * the width. * * @return the X-position of the center of the gradient * @see #setGradientCenter(float, float) */ public float getGradientCenterX() { return mGradientState.mCenterX; } /** * Returns the Y-position of the center of this gradient as a fraction of * the height. * * @return the Y-position of the center of the gradient * @see #setGradientCenter(float, float) */ public float getGradientCenterY() { return mGradientState.mCenterY; } /** * Sets the radius of the gradient. The radius is honored only when the * gradient type is set to {@link #RADIAL_GRADIENT}. *

* Note: 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. * * @param gradientRadius the radius of the gradient in pixels * * @see #mutate() * @see #setGradientType(int) * @see #getGradientRadius() */ 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 the radius of the gradient in pixels * @see #setGradientRadius(float) */ public float getGradientRadius() { if (mGradientState.mGradient != RADIAL_GRADIENT) { return 0; } ensureValidRect(); return mGradientRadius; } /** * Sets whether this drawable's {@code level} property will be used to * scale the gradient. If a gradient is not used, this property has no * effect. *

* Scaling behavior varies based on gradient type: *