2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.graphics.drawable;
19 import android.annotation.ColorInt;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.pm.ActivityInfo.Config;
24 import android.content.res.ColorStateList;
25 import android.content.res.Resources;
26 import android.content.res.Resources.Theme;
27 import android.content.res.TypedArray;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.ColorFilter;
31 import android.graphics.DashPathEffect;
32 import android.graphics.Insets;
33 import android.graphics.LinearGradient;
34 import android.graphics.Outline;
35 import android.graphics.Paint;
36 import android.graphics.Path;
37 import android.graphics.PixelFormat;
38 import android.graphics.PorterDuff;
39 import android.graphics.PorterDuffColorFilter;
40 import android.graphics.RadialGradient;
41 import android.graphics.Rect;
42 import android.graphics.RectF;
43 import android.graphics.Shader;
44 import android.graphics.SweepGradient;
45 import android.util.AttributeSet;
46 import android.util.DisplayMetrics;
47 import android.util.Log;
48 import android.util.TypedValue;
50 import com.android.internal.R;
52 import org.xmlpull.v1.XmlPullParser;
53 import org.xmlpull.v1.XmlPullParserException;
55 import java.io.IOException;
56 import java.lang.annotation.Retention;
57 import java.lang.annotation.RetentionPolicy;
60 * A Drawable with a color gradient for buttons, backgrounds, etc.
62 * <p>It can be defined in an XML file with the <code><shape></code> element. For more
63 * information, see the guide to <a
64 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
66 * @attr ref android.R.styleable#GradientDrawable_visible
67 * @attr ref android.R.styleable#GradientDrawable_shape
68 * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
69 * @attr ref android.R.styleable#GradientDrawable_innerRadius
70 * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
71 * @attr ref android.R.styleable#GradientDrawable_thickness
72 * @attr ref android.R.styleable#GradientDrawable_useLevel
73 * @attr ref android.R.styleable#GradientDrawableSize_width
74 * @attr ref android.R.styleable#GradientDrawableSize_height
75 * @attr ref android.R.styleable#GradientDrawableGradient_startColor
76 * @attr ref android.R.styleable#GradientDrawableGradient_centerColor
77 * @attr ref android.R.styleable#GradientDrawableGradient_endColor
78 * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
79 * @attr ref android.R.styleable#GradientDrawableGradient_angle
80 * @attr ref android.R.styleable#GradientDrawableGradient_type
81 * @attr ref android.R.styleable#GradientDrawableGradient_centerX
82 * @attr ref android.R.styleable#GradientDrawableGradient_centerY
83 * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius
84 * @attr ref android.R.styleable#GradientDrawableSolid_color
85 * @attr ref android.R.styleable#GradientDrawableStroke_width
86 * @attr ref android.R.styleable#GradientDrawableStroke_color
87 * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth
88 * @attr ref android.R.styleable#GradientDrawableStroke_dashGap
89 * @attr ref android.R.styleable#GradientDrawablePadding_left
90 * @attr ref android.R.styleable#GradientDrawablePadding_top
91 * @attr ref android.R.styleable#GradientDrawablePadding_right
92 * @attr ref android.R.styleable#GradientDrawablePadding_bottom
94 public class GradientDrawable extends Drawable {
96 * Shape is a rectangle, possibly with rounded corners
98 public static final int RECTANGLE = 0;
101 * Shape is an ellipse
103 public static final int OVAL = 1;
108 public static final int LINE = 2;
113 public static final int RING = 3;
116 @IntDef({RECTANGLE, OVAL, LINE, RING})
117 @Retention(RetentionPolicy.SOURCE)
118 public @interface Shape {}
121 * Gradient is linear (default.)
123 public static final int LINEAR_GRADIENT = 0;
126 * Gradient is circular.
128 public static final int RADIAL_GRADIENT = 1;
131 * Gradient is a sweep.
133 public static final int SWEEP_GRADIENT = 2;
136 @IntDef({LINEAR_GRADIENT, RADIAL_GRADIENT, SWEEP_GRADIENT})
137 @Retention(RetentionPolicy.SOURCE)
138 public @interface GradientType {}
140 /** Radius is in pixels. */
141 private static final int RADIUS_TYPE_PIXELS = 0;
143 /** Radius is a fraction of the base size. */
144 private static final int RADIUS_TYPE_FRACTION = 1;
146 /** Radius is a fraction of the bounds size. */
147 private static final int RADIUS_TYPE_FRACTION_PARENT = 2;
150 @IntDef({RADIUS_TYPE_PIXELS, RADIUS_TYPE_FRACTION, RADIUS_TYPE_FRACTION_PARENT})
151 @Retention(RetentionPolicy.SOURCE)
152 public @interface RadiusType {}
154 private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f;
155 private static final float DEFAULT_THICKNESS_RATIO = 9.0f;
157 private GradientState mGradientState;
159 private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
160 private Rect mPadding;
161 private Paint mStrokePaint; // optional, set by the caller
162 private ColorFilter mColorFilter; // optional, set by the caller
163 private PorterDuffColorFilter mTintFilter;
164 private int mAlpha = 0xFF; // modified by the caller
166 private final Path mPath = new Path();
167 private final RectF mRect = new RectF();
169 private Paint mLayerPaint; // internal, used if we use saveLayer()
170 private boolean mGradientIsDirty;
171 private boolean mMutated;
172 private Path mRingPath;
173 private boolean mPathIsDirty = true;
175 /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */
176 private float mGradientRadius;
179 * Controls how the gradient is oriented relative to the drawable's bounds
181 public enum Orientation {
182 /** draw the gradient from the top to the bottom */
184 /** draw the gradient from the top-right to the bottom-left */
186 /** draw the gradient from the right to the left */
188 /** draw the gradient from the bottom-right to the top-left */
190 /** draw the gradient from the bottom to the top */
192 /** draw the gradient from the bottom-left to the top-right */
194 /** draw the gradient from the left to the right */
196 /** draw the gradient from the top-left to the bottom-right */
200 public GradientDrawable() {
201 this(new GradientState(Orientation.TOP_BOTTOM, null), null);
205 * Create a new gradient drawable given an orientation and an array
206 * of colors for the gradient.
208 public GradientDrawable(Orientation orientation, @ColorInt int[] colors) {
209 this(new GradientState(orientation, colors), null);
213 public boolean getPadding(Rect padding) {
214 if (mPadding != null) {
215 padding.set(mPadding);
218 return super.getPadding(padding);
223 * Specifies radii for each of the 4 corners. For each corner, the array
224 * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
225 * ordered top-left, top-right, bottom-right, bottom-left. This property
226 * is honored only when the shape is of type {@link #RECTANGLE}.
228 * <strong>Note</strong>: changing this property will affect all instances
229 * of a drawable loaded from a resource. It is recommended to invoke
230 * {@link #mutate()} before changing this property.
232 * @param radii an array of length >= 8 containing 4 pairs of X and Y
233 * radius for each corner, specified in pixels
236 * @see #setShape(int)
237 * @see #setCornerRadius(float)
239 public void setCornerRadii(@Nullable float[] radii) {
240 mGradientState.setCornerRadii(radii);
246 * Returns the radii for each of the 4 corners. For each corner, the array
247 * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are
248 * ordered top-left, top-right, bottom-right, bottom-left.
250 * If the radius was previously set with {@link #setCornerRadius(float)},
251 * or if the corners are not rounded, this method will return {@code null}.
253 * @return an array containing the radii for each of the 4 corners, or
255 * @see #setCornerRadii(float[])
258 public float[] getCornerRadii() {
259 return mGradientState.mRadiusArray.clone();
263 * Specifies the radius for the corners of the gradient. If this is > 0,
264 * then the drawable is drawn in a round-rectangle, rather than a
265 * rectangle. This property is honored only when the shape is of type
266 * {@link #RECTANGLE}.
268 * <strong>Note</strong>: changing this property will affect all instances
269 * of a drawable loaded from a resource. It is recommended to invoke
270 * {@link #mutate()} before changing this property.
272 * @param radius The radius in pixels of the corners of the rectangle shape
275 * @see #setCornerRadii(float[])
276 * @see #setShape(int)
278 public void setCornerRadius(float radius) {
279 mGradientState.setCornerRadius(radius);
285 * Returns the radius for the corners of the gradient.
287 * If the radius was previously set with {@link #setCornerRadii(float[])},
288 * or if the corners are not rounded, this method will return {@code null}.
290 * @return the radius in pixels of the corners of the rectangle shape, or 0
291 * @see #setCornerRadius
293 public float getCornerRadius() {
294 return mGradientState.mRadius;
298 * <p>Set the stroke width and color for the drawable. If width is zero,
299 * then no stroke is drawn.</p>
300 * <p><strong>Note</strong>: changing this property will affect all instances
301 * of a drawable loaded from a resource. It is recommended to invoke
302 * {@link #mutate()} before changing this property.</p>
304 * @param width The width in pixels of the stroke
305 * @param color The color of the stroke
308 * @see #setStroke(int, int, float, float)
310 public void setStroke(int width, @ColorInt int color) {
311 setStroke(width, color, 0, 0);
315 * <p>Set the stroke width and color state list for the drawable. If width
316 * is zero, then no stroke is drawn.</p>
317 * <p><strong>Note</strong>: changing this property will affect all instances
318 * of a drawable loaded from a resource. It is recommended to invoke
319 * {@link #mutate()} before changing this property.</p>
321 * @param width The width in pixels of the stroke
322 * @param colorStateList The color state list of the stroke
325 * @see #setStroke(int, ColorStateList, float, float)
327 public void setStroke(int width, ColorStateList colorStateList) {
328 setStroke(width, colorStateList, 0, 0);
332 * <p>Set the stroke width and color for the drawable. If width is zero,
333 * then no stroke is drawn. This method can also be used to dash the stroke.</p>
334 * <p><strong>Note</strong>: changing this property will affect all instances
335 * of a drawable loaded from a resource. It is recommended to invoke
336 * {@link #mutate()} before changing this property.</p>
338 * @param width The width in pixels of the stroke
339 * @param color The color of the stroke
340 * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
341 * @param dashGap The gap in pixels between dashes
344 * @see #setStroke(int, int)
346 public void setStroke(int width, @ColorInt int color, float dashWidth, float dashGap) {
347 mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap);
348 setStrokeInternal(width, color, dashWidth, dashGap);
352 * <p>Set the stroke width and color state list for the drawable. If width
353 * is zero, then no stroke is drawn. This method can also be used to dash
355 * <p><strong>Note</strong>: changing this property will affect all instances
356 * of a drawable loaded from a resource. It is recommended to invoke
357 * {@link #mutate()} before changing this property.</p>
359 * @param width The width in pixels of the stroke
360 * @param colorStateList The color state list of the stroke
361 * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
362 * @param dashGap The gap in pixels between dashes
365 * @see #setStroke(int, ColorStateList)
367 public void setStroke(
368 int width, ColorStateList colorStateList, float dashWidth, float dashGap) {
369 mGradientState.setStroke(width, colorStateList, dashWidth, dashGap);
371 if (colorStateList == null) {
372 color = Color.TRANSPARENT;
374 final int[] stateSet = getState();
375 color = colorStateList.getColorForState(stateSet, 0);
377 setStrokeInternal(width, color, dashWidth, dashGap);
380 private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) {
381 if (mStrokePaint == null) {
382 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
383 mStrokePaint.setStyle(Paint.Style.STROKE);
385 mStrokePaint.setStrokeWidth(width);
386 mStrokePaint.setColor(color);
388 DashPathEffect e = null;
390 e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
392 mStrokePaint.setPathEffect(e);
398 * <p>Sets the size of the shape drawn by this drawable.</p>
399 * <p><strong>Note</strong>: changing this property will affect all instances
400 * of a drawable loaded from a resource. It is recommended to invoke
401 * {@link #mutate()} before changing this property.</p>
403 * @param width The width of the shape used by this drawable
404 * @param height The height of the shape used by this drawable
407 * @see #setGradientType(int)
409 public void setSize(int width, int height) {
410 mGradientState.setSize(width, height);
416 * <p>Sets the type of shape used to draw the gradient.</p>
417 * <p><strong>Note</strong>: changing this property will affect all instances
418 * of a drawable loaded from a resource. It is recommended to invoke
419 * {@link #mutate()} before changing this property.</p>
421 * @param shape The desired shape for this drawable: {@link #LINE},
422 * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}
426 public void setShape(@Shape int shape) {
429 mGradientState.setShape(shape);
434 * Returns the type of shape used by this drawable, one of {@link #LINE},
435 * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}.
437 * @return the type of shape used by this drawable
438 * @see #setShape(int)
441 public int getShape() {
442 return mGradientState.mShape;
446 * Sets the type of gradient used by this drawable.
448 * <strong>Note</strong>: changing this property will affect all instances
449 * of a drawable loaded from a resource. It is recommended to invoke
450 * {@link #mutate()} before changing this property.
452 * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT},
453 * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}
456 * @see #getGradientType()
458 public void setGradientType(@GradientType int gradient) {
459 mGradientState.setGradientType(gradient);
460 mGradientIsDirty = true;
465 * Returns the type of gradient used by this drawable, one of
466 * {@link #LINEAR_GRADIENT}, {@link #RADIAL_GRADIENT}, or
467 * {@link #SWEEP_GRADIENT}.
469 * @return the type of gradient used by this drawable
470 * @see #setGradientType(int)
473 public int getGradientType() {
474 return mGradientState.mGradient;
478 * Sets the center location in pixels of the gradient. The radius is
479 * honored only when the gradient type is set to {@link #RADIAL_GRADIENT}
480 * or {@link #SWEEP_GRADIENT}.
482 * <strong>Note</strong>: changing this property will affect all instances
483 * of a drawable loaded from a resource. It is recommended to invoke
484 * {@link #mutate()} before changing this property.
486 * @param x the x coordinate of the gradient's center in pixels
487 * @param y the y coordinate of the gradient's center in pixels
490 * @see #setGradientType(int)
491 * @see #getGradientCenterX()
492 * @see #getGradientCenterY()
494 public void setGradientCenter(float x, float y) {
495 mGradientState.setGradientCenter(x, y);
496 mGradientIsDirty = true;
501 * Returns the center X location of this gradient in pixels.
503 * @return the center X location of this gradient in pixels
504 * @see #setGradientCenter(float, float)
506 public float getGradientCenterX() {
507 return mGradientState.mCenterX;
511 * Returns the center Y location of this gradient in pixels.
513 * @return the center Y location of this gradient in pixels
514 * @see #setGradientCenter(float, float)
516 public float getGradientCenterY() {
517 return mGradientState.mCenterY;
521 * Sets the radius of the gradient. The radius is honored only when the
522 * gradient type is set to {@link #RADIAL_GRADIENT}.
524 * <strong>Note</strong>: changing this property will affect all instances
525 * of a drawable loaded from a resource. It is recommended to invoke
526 * {@link #mutate()} before changing this property.
528 * @param gradientRadius the radius of the gradient in pixels
531 * @see #setGradientType(int)
532 * @see #getGradientRadius()
534 public void setGradientRadius(float gradientRadius) {
535 mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX);
536 mGradientIsDirty = true;
541 * Returns the radius of the gradient in pixels. The radius is valid only
542 * when the gradient type is set to {@link #RADIAL_GRADIENT}.
544 * @return the radius of the gradient in pixels
545 * @see #setGradientRadius(float)
547 public float getGradientRadius() {
548 if (mGradientState.mGradient != RADIAL_GRADIENT) {
553 return mGradientRadius;
557 * Sets whether or not this drawable will honor its {@code level} property.
559 * <strong>Note</strong>: changing this property will affect all instances
560 * of a drawable loaded from a resource. It is recommended to invoke
561 * {@link #mutate()} before changing this property.
563 * @param useLevel {@code true} if this drawable should honor its level,
564 * {@code false} otherwise
567 * @see #setLevel(int)
569 * @see #getUseLevel()
571 public void setUseLevel(boolean useLevel) {
572 mGradientState.mUseLevel = useLevel;
573 mGradientIsDirty = true;
578 * Returns whether or not this drawable will honor its {@code level}
581 * @return {@code true} if this drawable should honor its level,
582 * {@code false} otherwise
583 * @see #setUseLevel(boolean)
585 public boolean getUseLevel() {
586 return mGradientState.mUseLevel;
589 private int modulateAlpha(int alpha) {
590 int scale = mAlpha + (mAlpha >> 7);
591 return alpha * scale >> 8;
595 * Returns the orientation of the gradient defined in this drawable.
597 * @return the orientation of the gradient defined in this drawable
598 * @see #setOrientation(Orientation)
600 public Orientation getOrientation() {
601 return mGradientState.mOrientation;
605 * Sets the orientation of the gradient defined in this drawable.
607 * <strong>Note</strong>: changing orientation will affect all instances
608 * of a drawable loaded from a resource. It is recommended to invoke
609 * {@link #mutate()} before changing the orientation.
611 * @param orientation the desired orientation (angle) of the gradient
614 * @see #getOrientation()
616 public void setOrientation(Orientation orientation) {
617 mGradientState.mOrientation = orientation;
618 mGradientIsDirty = true;
623 * Sets the colors used to draw the gradient.
625 * Each color is specified as an ARGB integer and the array must contain at
628 * <strong>Note</strong>: changing colors will affect all instances of a
629 * drawable loaded from a resource. It is recommended to invoke
630 * {@link #mutate()} before changing the colors.
632 * @param colors an array containing 2 or more ARGB colors
634 * @see #setColor(int)
636 public void setColors(@ColorInt int[] colors) {
637 mGradientState.setGradientColors(colors);
638 mGradientIsDirty = true;
643 * Returns the colors used to draw the gradient, or {@code null} if the
644 * gradient is drawn using a single color or no colors.
646 * @return the colors used to draw the gradient, or {@code null}
647 * @see #setColors(int[] colors)
650 public int[] getColors() {
651 return mGradientState.mGradientColors == null ?
652 null : mGradientState.mGradientColors.clone();
656 public void draw(Canvas canvas) {
657 if (!ensureValidRect()) {
662 // remember the alpha values, in case we temporarily overwrite them
663 // when we modulate them with mAlpha
664 final int prevFillAlpha = mFillPaint.getAlpha();
665 final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
666 // compute the modulate alpha values
667 final int currFillAlpha = modulateAlpha(prevFillAlpha);
668 final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
670 final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
671 mStrokePaint.getStrokeWidth() > 0;
672 final boolean haveFill = currFillAlpha > 0;
673 final GradientState st = mGradientState;
674 final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter;
676 /* we need a layer iff we're drawing both a fill and stroke, and the
677 stroke is non-opaque, and our shapetype actually supports
678 fill+stroke. Otherwise we can just draw the stroke (if any) on top
679 of the fill (if any) without worrying about blending artifacts.
681 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
682 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null);
684 /* Drawing with a layer is slower than direct drawing, but it
685 allows us to apply paint effects like alpha and colorfilter to
686 the result of multiple separate draws. In our case, if the user
687 asks for a non-opaque alpha value (via setAlpha), and we're
688 stroking, then we need to apply the alpha AFTER we've drawn
689 both the fill and the stroke.
692 if (mLayerPaint == null) {
693 mLayerPaint = new Paint();
695 mLayerPaint.setDither(st.mDither);
696 mLayerPaint.setAlpha(mAlpha);
697 mLayerPaint.setColorFilter(colorFilter);
699 float rad = mStrokePaint.getStrokeWidth();
700 canvas.saveLayer(mRect.left - rad, mRect.top - rad,
701 mRect.right + rad, mRect.bottom + rad,
702 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
704 // don't perform the filter in our individual paints
705 // since the layer will do it for us
706 mFillPaint.setColorFilter(null);
707 mStrokePaint.setColorFilter(null);
709 /* if we're not using a layer, apply the dither/filter to our
712 mFillPaint.setAlpha(currFillAlpha);
713 mFillPaint.setDither(st.mDither);
714 mFillPaint.setColorFilter(colorFilter);
715 if (colorFilter != null && st.mSolidColors == null) {
716 mFillPaint.setColor(mAlpha << 24);
719 mStrokePaint.setAlpha(currStrokeAlpha);
720 mStrokePaint.setDither(st.mDither);
721 mStrokePaint.setColorFilter(colorFilter);
727 if (st.mRadiusArray != null) {
729 canvas.drawPath(mPath, mFillPaint);
731 canvas.drawPath(mPath, mStrokePaint);
733 } else if (st.mRadius > 0.0f) {
734 // since the caller is only giving us 1 value, we will force
735 // it to be square if the rect is too small in one dimension
736 // to show it. If we did nothing, Skia would clamp the rad
737 // independently along each axis, giving us a thin ellipse
738 // if the rect were very wide but not very tall
739 float rad = Math.min(st.mRadius,
740 Math.min(mRect.width(), mRect.height()) * 0.5f);
741 canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
743 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
746 if (mFillPaint.getColor() != 0 || colorFilter != null ||
747 mFillPaint.getShader() != null) {
748 canvas.drawRect(mRect, mFillPaint);
751 canvas.drawRect(mRect, mStrokePaint);
756 canvas.drawOval(mRect, mFillPaint);
758 canvas.drawOval(mRect, mStrokePaint);
763 float y = r.centerY();
765 canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
770 Path path = buildRing(st);
771 canvas.drawPath(path, mFillPaint);
773 canvas.drawPath(path, mStrokePaint);
781 mFillPaint.setAlpha(prevFillAlpha);
783 mStrokePaint.setAlpha(prevStrokeAlpha);
788 private void buildPathIfDirty() {
789 final GradientState st = mGradientState;
793 mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
794 mPathIsDirty = false;
798 private Path buildRing(GradientState st) {
799 if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
800 mPathIsDirty = false;
802 float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
804 RectF bounds = new RectF(mRect);
806 float x = bounds.width() / 2.0f;
807 float y = bounds.height() / 2.0f;
809 float thickness = st.mThickness != -1 ?
810 st.mThickness : bounds.width() / st.mThicknessRatio;
812 float radius = st.mInnerRadius != -1 ?
813 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
815 RectF innerBounds = new RectF(bounds);
816 innerBounds.inset(x - radius, y - radius);
818 bounds = new RectF(innerBounds);
819 bounds.inset(-thickness, -thickness);
821 if (mRingPath == null) {
822 mRingPath = new Path();
827 final Path ringPath = mRingPath;
828 // arcTo treats the sweep angle mod 360, so check for that, since we
829 // think 360 means draw the entire oval
830 if (sweep < 360 && sweep > -360) {
831 ringPath.setFillType(Path.FillType.EVEN_ODD);
833 ringPath.moveTo(x + radius, y);
835 ringPath.lineTo(x + radius + thickness, y);
837 ringPath.arcTo(bounds, 0.0f, sweep, false);
839 ringPath.arcTo(innerBounds, sweep, -sweep, false);
842 // add the entire ovals
843 ringPath.addOval(bounds, Path.Direction.CW);
844 ringPath.addOval(innerBounds, Path.Direction.CCW);
851 * Changes this drawable to use a single color instead of a gradient.
853 * <strong>Note</strong>: changing color will affect all instances of a
854 * drawable loaded from a resource. It is recommended to invoke
855 * {@link #mutate()} before changing the color.
857 * @param argb The color used to fill the shape
860 * @see #setColors(int[])
863 public void setColor(@ColorInt int argb) {
864 mGradientState.setSolidColors(ColorStateList.valueOf(argb));
865 mFillPaint.setColor(argb);
870 * Changes this drawable to use a single color state list instead of a
871 * gradient. Calling this method with a null argument will clear the color
872 * and is equivalent to calling {@link #setColor(int)} with the argument
873 * {@link Color#TRANSPARENT}.
875 * <strong>Note</strong>: changing color will affect all instances of a
876 * drawable loaded from a resource. It is recommended to invoke
877 * {@link #mutate()} before changing the color.</p>
879 * @param colorStateList The color state list used to fill the shape
884 public void setColor(@Nullable ColorStateList colorStateList) {
885 mGradientState.setSolidColors(colorStateList);
887 if (colorStateList == null) {
888 color = Color.TRANSPARENT;
890 final int[] stateSet = getState();
891 color = colorStateList.getColorForState(stateSet, 0);
893 mFillPaint.setColor(color);
898 * Returns the color state list used to fill the shape, or {@code null} if
899 * the shape is filled with a gradient or has no fill color.
901 * @return the color state list used to fill this gradient, or {@code null}
903 * @see #setColor(int)
904 * @see #setColor(ColorStateList)
907 public ColorStateList getColor() {
908 return mGradientState.mSolidColors;
912 protected boolean onStateChange(int[] stateSet) {
913 boolean invalidateSelf = false;
915 final GradientState s = mGradientState;
916 final ColorStateList solidColors = s.mSolidColors;
917 if (solidColors != null) {
918 final int newColor = solidColors.getColorForState(stateSet, 0);
919 final int oldColor = mFillPaint.getColor();
920 if (oldColor != newColor) {
921 mFillPaint.setColor(newColor);
922 invalidateSelf = true;
926 final Paint strokePaint = mStrokePaint;
927 if (strokePaint != null) {
928 final ColorStateList strokeColors = s.mStrokeColors;
929 if (strokeColors != null) {
930 final int newColor = strokeColors.getColorForState(stateSet, 0);
931 final int oldColor = strokePaint.getColor();
932 if (oldColor != newColor) {
933 strokePaint.setColor(newColor);
934 invalidateSelf = true;
939 if (s.mTint != null && s.mTintMode != null) {
940 mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode);
941 invalidateSelf = true;
944 if (invalidateSelf) {
953 public boolean isStateful() {
954 final GradientState s = mGradientState;
955 return super.isStateful()
956 || (s.mSolidColors != null && s.mSolidColors.isStateful())
957 || (s.mStrokeColors != null && s.mStrokeColors.isStateful())
958 || (s.mTint != null && s.mTint.isStateful());
962 public @Config int getChangingConfigurations() {
963 return super.getChangingConfigurations() | mGradientState.getChangingConfigurations();
967 public void setAlpha(int alpha) {
968 if (alpha != mAlpha) {
975 public int getAlpha() {
980 public void setDither(boolean dither) {
981 if (dither != mGradientState.mDither) {
982 mGradientState.mDither = dither;
989 public ColorFilter getColorFilter() {
994 public void setColorFilter(@Nullable ColorFilter colorFilter) {
995 if (colorFilter != mColorFilter) {
996 mColorFilter = colorFilter;
1002 public void setTintList(@Nullable ColorStateList tint) {
1003 mGradientState.mTint = tint;
1004 mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode);
1009 public void setTintMode(@Nullable PorterDuff.Mode tintMode) {
1010 mGradientState.mTintMode = tintMode;
1011 mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode);
1016 public int getOpacity() {
1017 return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ?
1018 PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
1022 protected void onBoundsChange(Rect r) {
1023 super.onBoundsChange(r);
1025 mPathIsDirty = true;
1026 mGradientIsDirty = true;
1030 protected boolean onLevelChange(int level) {
1031 super.onLevelChange(level);
1032 mGradientIsDirty = true;
1033 mPathIsDirty = true;
1039 * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
1040 * rectangle (mRect) and the gradient itself, since it depends on our
1042 * @return true if the resulting rectangle is not empty, false otherwise
1044 private boolean ensureValidRect() {
1045 if (mGradientIsDirty) {
1046 mGradientIsDirty = false;
1048 Rect bounds = getBounds();
1051 if (mStrokePaint != null) {
1052 inset = mStrokePaint.getStrokeWidth() * 0.5f;
1055 final GradientState st = mGradientState;
1057 mRect.set(bounds.left + inset, bounds.top + inset,
1058 bounds.right - inset, bounds.bottom - inset);
1060 final int[] gradientColors = st.mGradientColors;
1061 if (gradientColors != null) {
1062 final RectF r = mRect;
1063 final float x0, x1, y0, y1;
1065 if (st.mGradient == LINEAR_GRADIENT) {
1066 final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
1067 switch (st.mOrientation) {
1069 x0 = r.left; y0 = r.top;
1070 x1 = x0; y1 = level * r.bottom;
1073 x0 = r.right; y0 = r.top;
1074 x1 = level * r.left; y1 = level * r.bottom;
1077 x0 = r.right; y0 = r.top;
1078 x1 = level * r.left; y1 = y0;
1081 x0 = r.right; y0 = r.bottom;
1082 x1 = level * r.left; y1 = level * r.top;
1085 x0 = r.left; y0 = r.bottom;
1086 x1 = x0; y1 = level * r.top;
1089 x0 = r.left; y0 = r.bottom;
1090 x1 = level * r.right; y1 = level * r.top;
1093 x0 = r.left; y0 = r.top;
1094 x1 = level * r.right; y1 = y0;
1097 x0 = r.left; y0 = r.top;
1098 x1 = level * r.right; y1 = level * r.bottom;
1102 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
1103 gradientColors, st.mPositions, Shader.TileMode.CLAMP));
1104 } else if (st.mGradient == RADIAL_GRADIENT) {
1105 x0 = r.left + (r.right - r.left) * st.mCenterX;
1106 y0 = r.top + (r.bottom - r.top) * st.mCenterY;
1108 float radius = st.mGradientRadius;
1109 if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
1110 // Fall back to parent width or height if intrinsic
1111 // size is not specified.
1112 final float width = st.mWidth >= 0 ? st.mWidth : r.width();
1113 final float height = st.mHeight >= 0 ? st.mHeight : r.height();
1114 radius *= Math.min(width, height);
1115 } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
1116 radius *= Math.min(r.width(), r.height());
1120 radius *= getLevel() / 10000.0f;
1123 mGradientRadius = radius;
1126 // We can't have a shader with non-positive radius, so
1127 // let's have a very, very small radius.
1131 mFillPaint.setShader(new RadialGradient(
1132 x0, y0, radius, gradientColors, null, Shader.TileMode.CLAMP));
1133 } else if (st.mGradient == SWEEP_GRADIENT) {
1134 x0 = r.left + (r.right - r.left) * st.mCenterX;
1135 y0 = r.top + (r.bottom - r.top) * st.mCenterY;
1137 int[] tempColors = gradientColors;
1138 float[] tempPositions = null;
1141 tempColors = st.mTempColors;
1142 final int length = gradientColors.length;
1143 if (tempColors == null || tempColors.length != length + 1) {
1144 tempColors = st.mTempColors = new int[length + 1];
1146 System.arraycopy(gradientColors, 0, tempColors, 0, length);
1147 tempColors[length] = gradientColors[length - 1];
1149 tempPositions = st.mTempPositions;
1150 final float fraction = 1.0f / (length - 1);
1151 if (tempPositions == null || tempPositions.length != length + 1) {
1152 tempPositions = st.mTempPositions = new float[length + 1];
1155 final float level = getLevel() / 10000.0f;
1156 for (int i = 0; i < length; i++) {
1157 tempPositions[i] = i * fraction * level;
1159 tempPositions[length] = 1.0f;
1162 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
1165 // If we don't have a solid color, the alpha channel must be
1166 // maxed out so that alpha modulation works correctly.
1167 if (st.mSolidColors == null) {
1168 mFillPaint.setColor(Color.BLACK);
1172 return !mRect.isEmpty();
1176 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
1177 @NonNull AttributeSet attrs, @Nullable Theme theme)
1178 throws XmlPullParserException, IOException {
1179 super.inflate(r, parser, attrs, theme);
1181 mGradientState.setDensity(Drawable.resolveDensity(r, 0));
1183 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
1184 updateStateFromTypedArray(a);
1187 inflateChildElements(r, parser, attrs, theme);
1189 updateLocalState(r);
1193 public void applyTheme(@NonNull Theme t) {
1194 super.applyTheme(t);
1196 final GradientState state = mGradientState;
1197 if (state == null) {
1201 state.setDensity(Drawable.resolveDensity(t.getResources(), 0));
1203 if (state.mThemeAttrs != null) {
1204 final TypedArray a = t.resolveAttributes(
1205 state.mThemeAttrs, R.styleable.GradientDrawable);
1206 updateStateFromTypedArray(a);
1210 if (state.mTint != null && state.mTint.canApplyTheme()) {
1211 state.mTint = state.mTint.obtainForTheme(t);
1214 if (state.mSolidColors != null && state.mSolidColors.canApplyTheme()) {
1215 state.mSolidColors = state.mSolidColors.obtainForTheme(t);
1218 if (state.mStrokeColors != null && state.mStrokeColors.canApplyTheme()) {
1219 state.mStrokeColors = state.mStrokeColors.obtainForTheme(t);
1222 applyThemeChildElements(t);
1224 updateLocalState(t.getResources());
1228 * Updates the constant state from the values in the typed array.
1230 private void updateStateFromTypedArray(TypedArray a) {
1231 final GradientState state = mGradientState;
1233 // Account for any configuration changes.
1234 state.mChangingConfigurations |= a.getChangingConfigurations();
1236 // Extract the theme attributes, if any.
1237 state.mThemeAttrs = a.extractThemeAttrs();
1239 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
1240 state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
1242 if (state.mShape == RING) {
1243 state.mInnerRadius = a.getDimensionPixelSize(
1244 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius);
1246 if (state.mInnerRadius == -1) {
1247 state.mInnerRadiusRatio = a.getFloat(
1248 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio);
1251 state.mThickness = a.getDimensionPixelSize(
1252 R.styleable.GradientDrawable_thickness, state.mThickness);
1254 if (state.mThickness == -1) {
1255 state.mThicknessRatio = a.getFloat(
1256 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio);
1259 state.mUseLevelForShape = a.getBoolean(
1260 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
1263 final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
1264 if (tintMode != -1) {
1265 state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN);
1268 final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
1273 final int insetLeft = a.getDimensionPixelSize(
1274 R.styleable.GradientDrawable_opticalInsetLeft, state.mOpticalInsets.left);
1275 final int insetTop = a.getDimensionPixelSize(
1276 R.styleable.GradientDrawable_opticalInsetTop, state.mOpticalInsets.top);
1277 final int insetRight = a.getDimensionPixelSize(
1278 R.styleable.GradientDrawable_opticalInsetRight, state.mOpticalInsets.right);
1279 final int insetBottom = a.getDimensionPixelSize(
1280 R.styleable.GradientDrawable_opticalInsetBottom, state.mOpticalInsets.bottom);
1281 state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom);
1285 public boolean canApplyTheme() {
1286 return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme();
1289 private void applyThemeChildElements(Theme t) {
1290 final GradientState st = mGradientState;
1292 if (st.mAttrSize != null) {
1293 final TypedArray a = t.resolveAttributes(
1294 st.mAttrSize, R.styleable.GradientDrawableSize);
1295 updateGradientDrawableSize(a);
1299 if (st.mAttrGradient != null) {
1300 final TypedArray a = t.resolveAttributes(
1301 st.mAttrGradient, R.styleable.GradientDrawableGradient);
1303 updateGradientDrawableGradient(t.getResources(), a);
1304 } catch (XmlPullParserException e) {
1305 rethrowAsRuntimeException(e);
1311 if (st.mAttrSolid != null) {
1312 final TypedArray a = t.resolveAttributes(
1313 st.mAttrSolid, R.styleable.GradientDrawableSolid);
1314 updateGradientDrawableSolid(a);
1318 if (st.mAttrStroke != null) {
1319 final TypedArray a = t.resolveAttributes(
1320 st.mAttrStroke, R.styleable.GradientDrawableStroke);
1321 updateGradientDrawableStroke(a);
1325 if (st.mAttrCorners != null) {
1326 final TypedArray a = t.resolveAttributes(
1327 st.mAttrCorners, R.styleable.DrawableCorners);
1328 updateDrawableCorners(a);
1332 if (st.mAttrPadding != null) {
1333 final TypedArray a = t.resolveAttributes(
1334 st.mAttrPadding, R.styleable.GradientDrawablePadding);
1335 updateGradientDrawablePadding(a);
1340 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
1341 Theme theme) throws XmlPullParserException, IOException {
1345 final int innerDepth = parser.getDepth() + 1;
1347 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1348 && ((depth=parser.getDepth()) >= innerDepth
1349 || type != XmlPullParser.END_TAG)) {
1350 if (type != XmlPullParser.START_TAG) {
1354 if (depth > innerDepth) {
1358 String name = parser.getName();
1360 if (name.equals("size")) {
1361 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
1362 updateGradientDrawableSize(a);
1364 } else if (name.equals("gradient")) {
1365 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
1366 updateGradientDrawableGradient(r, a);
1368 } else if (name.equals("solid")) {
1369 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
1370 updateGradientDrawableSolid(a);
1372 } else if (name.equals("stroke")) {
1373 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
1374 updateGradientDrawableStroke(a);
1376 } else if (name.equals("corners")) {
1377 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
1378 updateDrawableCorners(a);
1380 } else if (name.equals("padding")) {
1381 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
1382 updateGradientDrawablePadding(a);
1385 Log.w("drawable", "Bad element under <shape>: " + name);
1390 private void updateGradientDrawablePadding(TypedArray a) {
1391 final GradientState st = mGradientState;
1393 // Account for any configuration changes.
1394 st.mChangingConfigurations |= a.getChangingConfigurations();
1396 // Extract the theme attributes, if any.
1397 st.mAttrPadding = a.extractThemeAttrs();
1399 if (st.mPadding == null) {
1400 st.mPadding = new Rect();
1403 final Rect pad = st.mPadding;
1404 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
1405 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
1406 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right),
1407 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom));
1411 private void updateDrawableCorners(TypedArray a) {
1412 final GradientState st = mGradientState;
1414 // Account for any configuration changes.
1415 st.mChangingConfigurations |= a.getChangingConfigurations();
1417 // Extract the theme attributes, if any.
1418 st.mAttrCorners = a.extractThemeAttrs();
1420 final int radius = a.getDimensionPixelSize(
1421 R.styleable.DrawableCorners_radius, (int) st.mRadius);
1422 setCornerRadius(radius);
1424 // TODO: Update these to be themeable.
1425 final int topLeftRadius = a.getDimensionPixelSize(
1426 R.styleable.DrawableCorners_topLeftRadius, radius);
1427 final int topRightRadius = a.getDimensionPixelSize(
1428 R.styleable.DrawableCorners_topRightRadius, radius);
1429 final int bottomLeftRadius = a.getDimensionPixelSize(
1430 R.styleable.DrawableCorners_bottomLeftRadius, radius);
1431 final int bottomRightRadius = a.getDimensionPixelSize(
1432 R.styleable.DrawableCorners_bottomRightRadius, radius);
1433 if (topLeftRadius != radius || topRightRadius != radius ||
1434 bottomLeftRadius != radius || bottomRightRadius != radius) {
1435 // The corner radii are specified in clockwise order (see Path.addRoundRect())
1436 setCornerRadii(new float[] {
1437 topLeftRadius, topLeftRadius,
1438 topRightRadius, topRightRadius,
1439 bottomRightRadius, bottomRightRadius,
1440 bottomLeftRadius, bottomLeftRadius
1445 private void updateGradientDrawableStroke(TypedArray a) {
1446 final GradientState st = mGradientState;
1448 // Account for any configuration changes.
1449 st.mChangingConfigurations |= a.getChangingConfigurations();
1451 // Extract the theme attributes, if any.
1452 st.mAttrStroke = a.extractThemeAttrs();
1454 // We have an explicit stroke defined, so the default stroke width
1455 // must be at least 0 or the current stroke width.
1456 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth);
1457 final int width = a.getDimensionPixelSize(
1458 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth);
1459 final float dashWidth = a.getDimension(
1460 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth);
1462 ColorStateList colorStateList = a.getColorStateList(
1463 R.styleable.GradientDrawableStroke_color);
1464 if (colorStateList == null) {
1465 colorStateList = st.mStrokeColors;
1468 if (dashWidth != 0.0f) {
1469 final float dashGap = a.getDimension(
1470 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap);
1471 setStroke(width, colorStateList, dashWidth, dashGap);
1473 setStroke(width, colorStateList);
1477 private void updateGradientDrawableSolid(TypedArray a) {
1478 final GradientState st = mGradientState;
1480 // Account for any configuration changes.
1481 st.mChangingConfigurations |= a.getChangingConfigurations();
1483 // Extract the theme attributes, if any.
1484 st.mAttrSolid = a.extractThemeAttrs();
1486 final ColorStateList colorStateList = a.getColorStateList(
1487 R.styleable.GradientDrawableSolid_color);
1488 if (colorStateList != null) {
1489 setColor(colorStateList);
1493 private void updateGradientDrawableGradient(Resources r, TypedArray a)
1494 throws XmlPullParserException {
1495 final GradientState st = mGradientState;
1497 // Account for any configuration changes.
1498 st.mChangingConfigurations |= a.getChangingConfigurations();
1500 // Extract the theme attributes, if any.
1501 st.mAttrGradient = a.extractThemeAttrs();
1503 st.mCenterX = getFloatOrFraction(
1504 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX);
1505 st.mCenterY = getFloatOrFraction(
1506 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY);
1507 st.mUseLevel = a.getBoolean(
1508 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel);
1509 st.mGradient = a.getInt(
1510 R.styleable.GradientDrawableGradient_type, st.mGradient);
1512 // TODO: Update these to be themeable.
1513 final int startColor = a.getColor(
1514 R.styleable.GradientDrawableGradient_startColor, 0);
1515 final boolean hasCenterColor = a.hasValue(
1516 R.styleable.GradientDrawableGradient_centerColor);
1517 final int centerColor = a.getColor(
1518 R.styleable.GradientDrawableGradient_centerColor, 0);
1519 final int endColor = a.getColor(
1520 R.styleable.GradientDrawableGradient_endColor, 0);
1522 if (hasCenterColor) {
1523 st.mGradientColors = new int[3];
1524 st.mGradientColors[0] = startColor;
1525 st.mGradientColors[1] = centerColor;
1526 st.mGradientColors[2] = endColor;
1528 st.mPositions = new float[3];
1529 st.mPositions[0] = 0.0f;
1530 // Since 0.5f is default value, try to take the one that isn't 0.5f
1531 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
1532 st.mPositions[2] = 1f;
1534 st.mGradientColors = new int[2];
1535 st.mGradientColors[0] = startColor;
1536 st.mGradientColors[1] = endColor;
1539 if (st.mGradient == LINEAR_GRADIENT) {
1540 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
1543 if (angle % 45 != 0) {
1544 throw new XmlPullParserException(a.getPositionDescription()
1545 + "<gradient> tag requires 'angle' attribute to "
1546 + "be a multiple of 45");
1553 st.mOrientation = Orientation.LEFT_RIGHT;
1556 st.mOrientation = Orientation.BL_TR;
1559 st.mOrientation = Orientation.BOTTOM_TOP;
1562 st.mOrientation = Orientation.BR_TL;
1565 st.mOrientation = Orientation.RIGHT_LEFT;
1568 st.mOrientation = Orientation.TR_BL;
1571 st.mOrientation = Orientation.TOP_BOTTOM;
1574 st.mOrientation = Orientation.TL_BR;
1578 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
1581 final @RadiusType int radiusType;
1582 if (tv.type == TypedValue.TYPE_FRACTION) {
1583 radius = tv.getFraction(1.0f, 1.0f);
1585 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
1586 & TypedValue.COMPLEX_UNIT_MASK;
1587 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
1588 radiusType = RADIUS_TYPE_FRACTION_PARENT;
1590 radiusType = RADIUS_TYPE_FRACTION;
1592 } else if (tv.type == TypedValue.TYPE_DIMENSION) {
1593 radius = tv.getDimension(r.getDisplayMetrics());
1594 radiusType = RADIUS_TYPE_PIXELS;
1596 radius = tv.getFloat();
1597 radiusType = RADIUS_TYPE_PIXELS;
1600 st.mGradientRadius = radius;
1601 st.mGradientRadiusType = radiusType;
1602 } else if (st.mGradient == RADIAL_GRADIENT) {
1603 throw new XmlPullParserException(
1604 a.getPositionDescription()
1605 + "<gradient> tag requires 'gradientRadius' "
1606 + "attribute with radial type");
1611 private void updateGradientDrawableSize(TypedArray a) {
1612 final GradientState st = mGradientState;
1614 // Account for any configuration changes.
1615 st.mChangingConfigurations |= a.getChangingConfigurations();
1617 // Extract the theme attributes, if any.
1618 st.mAttrSize = a.extractThemeAttrs();
1620 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth);
1621 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight);
1624 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
1625 TypedValue tv = a.peekValue(index);
1626 float v = defaultValue;
1628 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
1629 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
1635 public int getIntrinsicWidth() {
1636 return mGradientState.mWidth;
1640 public int getIntrinsicHeight() {
1641 return mGradientState.mHeight;
1646 public Insets getOpticalInsets() {
1647 return mGradientState.mOpticalInsets;
1651 public ConstantState getConstantState() {
1652 mGradientState.mChangingConfigurations = getChangingConfigurations();
1653 return mGradientState;
1656 private boolean isOpaqueForState() {
1657 if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null
1658 && !isOpaque(mStrokePaint.getColor())) {
1662 // Don't check opacity if we're using a gradient, as we've already
1663 // checked the gradient opacity in mOpaqueOverShape.
1664 if (mGradientState.mGradientColors == null && !isOpaque(mFillPaint.getColor())) {
1672 public void getOutline(Outline outline) {
1673 final GradientState st = mGradientState;
1674 final Rect bounds = getBounds();
1675 // only report non-zero alpha if shape being drawn has consistent opacity over shape. Must
1676 // either not have a stroke, or have same stroke/fill opacity
1677 boolean useFillOpacity = st.mOpaqueOverShape && (mGradientState.mStrokeWidth <= 0
1678 || mStrokePaint == null
1679 || mStrokePaint.getAlpha() == mFillPaint.getAlpha());
1680 outline.setAlpha(useFillOpacity
1681 ? modulateAlpha(mFillPaint.getAlpha()) / 255.0f
1684 switch (st.mShape) {
1686 if (st.mRadiusArray != null) {
1688 outline.setConvexPath(mPath);
1693 if (st.mRadius > 0.0f) {
1694 // clamp the radius based on width & height, matching behavior in draw()
1695 rad = Math.min(st.mRadius,
1696 Math.min(bounds.width(), bounds.height()) * 0.5f);
1698 outline.setRoundRect(bounds, rad);
1701 outline.setOval(bounds);
1704 // Hairlines (0-width stroke) must have a non-empty outline for
1705 // shadows to draw correctly, so we'll use a very small width.
1706 final float halfStrokeWidth = mStrokePaint == null ?
1707 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f;
1708 final float centerY = bounds.centerY();
1709 final int top = (int) Math.floor(centerY - halfStrokeWidth);
1710 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
1712 outline.setRect(bounds.left, top, bounds.right, bottom);
1715 // TODO: support more complex shapes
1720 public Drawable mutate() {
1721 if (!mMutated && super.mutate() == this) {
1722 mGradientState = new GradientState(mGradientState, null);
1723 updateLocalState(null);
1732 public void clearMutated() {
1733 super.clearMutated();
1737 final static class GradientState extends ConstantState {
1738 public @Config int mChangingConfigurations;
1739 public @Shape int mShape = RECTANGLE;
1740 public @GradientType int mGradient = LINEAR_GRADIENT;
1741 public int mAngle = 0;
1742 public Orientation mOrientation;
1743 public ColorStateList mSolidColors;
1744 public ColorStateList mStrokeColors;
1745 public @ColorInt int[] mGradientColors;
1746 public @ColorInt int[] mTempColors; // no need to copy
1747 public float[] mTempPositions; // no need to copy
1748 public float[] mPositions;
1749 public int mStrokeWidth = -1; // if >= 0 use stroking.
1750 public float mStrokeDashWidth = 0.0f;
1751 public float mStrokeDashGap = 0.0f;
1752 public float mRadius = 0.0f; // use this if mRadiusArray is null
1753 public float[] mRadiusArray = null;
1754 public Rect mPadding = null;
1755 public int mWidth = -1;
1756 public int mHeight = -1;
1757 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
1758 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
1759 public int mInnerRadius = -1;
1760 public int mThickness = -1;
1761 public boolean mDither = false;
1762 public Insets mOpticalInsets = Insets.NONE;
1764 float mCenterX = 0.5f;
1765 float mCenterY = 0.5f;
1766 float mGradientRadius = 0.5f;
1767 @RadiusType int mGradientRadiusType = RADIUS_TYPE_PIXELS;
1768 boolean mUseLevel = false;
1769 boolean mUseLevelForShape = true;
1771 boolean mOpaqueOverBounds;
1772 boolean mOpaqueOverShape;
1774 ColorStateList mTint = null;
1775 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
1777 int mDensity = DisplayMetrics.DENSITY_DEFAULT;
1781 int[] mAttrGradient;
1787 public GradientState(Orientation orientation, int[] gradientColors) {
1788 mOrientation = orientation;
1789 setGradientColors(gradientColors);
1792 public GradientState(@NonNull GradientState orig, @Nullable Resources res) {
1793 mChangingConfigurations = orig.mChangingConfigurations;
1794 mShape = orig.mShape;
1795 mGradient = orig.mGradient;
1796 mAngle = orig.mAngle;
1797 mOrientation = orig.mOrientation;
1798 mSolidColors = orig.mSolidColors;
1799 if (orig.mGradientColors != null) {
1800 mGradientColors = orig.mGradientColors.clone();
1802 if (orig.mPositions != null) {
1803 mPositions = orig.mPositions.clone();
1805 mStrokeColors = orig.mStrokeColors;
1806 mStrokeWidth = orig.mStrokeWidth;
1807 mStrokeDashWidth = orig.mStrokeDashWidth;
1808 mStrokeDashGap = orig.mStrokeDashGap;
1809 mRadius = orig.mRadius;
1810 if (orig.mRadiusArray != null) {
1811 mRadiusArray = orig.mRadiusArray.clone();
1813 if (orig.mPadding != null) {
1814 mPadding = new Rect(orig.mPadding);
1816 mWidth = orig.mWidth;
1817 mHeight = orig.mHeight;
1818 mInnerRadiusRatio = orig.mInnerRadiusRatio;
1819 mThicknessRatio = orig.mThicknessRatio;
1820 mInnerRadius = orig.mInnerRadius;
1821 mThickness = orig.mThickness;
1822 mDither = orig.mDither;
1823 mOpticalInsets = orig.mOpticalInsets;
1824 mCenterX = orig.mCenterX;
1825 mCenterY = orig.mCenterY;
1826 mGradientRadius = orig.mGradientRadius;
1827 mGradientRadiusType = orig.mGradientRadiusType;
1828 mUseLevel = orig.mUseLevel;
1829 mUseLevelForShape = orig.mUseLevelForShape;
1830 mOpaqueOverBounds = orig.mOpaqueOverBounds;
1831 mOpaqueOverShape = orig.mOpaqueOverShape;
1833 mTintMode = orig.mTintMode;
1834 mThemeAttrs = orig.mThemeAttrs;
1835 mAttrSize = orig.mAttrSize;
1836 mAttrGradient = orig.mAttrGradient;
1837 mAttrSolid = orig.mAttrSolid;
1838 mAttrStroke = orig.mAttrStroke;
1839 mAttrCorners = orig.mAttrCorners;
1840 mAttrPadding = orig.mAttrPadding;
1842 mDensity = Drawable.resolveDensity(res, orig.mDensity);
1843 if (orig.mDensity != mDensity) {
1844 applyDensityScaling(orig.mDensity, mDensity);
1849 * Sets the constant state density.
1851 * If the density has been previously set, dispatches the change to
1852 * subclasses so that density-dependent properties may be scaled as
1855 * @param targetDensity the new constant state density
1857 public final void setDensity(int targetDensity) {
1858 if (mDensity != targetDensity) {
1859 final int sourceDensity = mDensity;
1860 mDensity = targetDensity;
1862 applyDensityScaling(sourceDensity, targetDensity);
1866 private void applyDensityScaling(int sourceDensity, int targetDensity) {
1867 if (mInnerRadius > 0) {
1868 mInnerRadius = Drawable.scaleFromDensity(
1869 mInnerRadius, sourceDensity, targetDensity, true);
1871 if (mThickness > 0) {
1872 mThickness = Drawable.scaleFromDensity(
1873 mThickness, sourceDensity, targetDensity, true);
1875 if (mOpticalInsets != Insets.NONE) {
1876 final int left = Drawable.scaleFromDensity(
1877 mOpticalInsets.left, sourceDensity, targetDensity, true);
1878 final int top = Drawable.scaleFromDensity(
1879 mOpticalInsets.top, sourceDensity, targetDensity, true);
1880 final int right = Drawable.scaleFromDensity(
1881 mOpticalInsets.right, sourceDensity, targetDensity, true);
1882 final int bottom = Drawable.scaleFromDensity(
1883 mOpticalInsets.bottom, sourceDensity, targetDensity, true);
1884 mOpticalInsets = Insets.of(left, top, right, bottom);
1886 if (mPadding != null) {
1887 mPadding.left = Drawable.scaleFromDensity(
1888 mPadding.left, sourceDensity, targetDensity, false);
1889 mPadding.top = Drawable.scaleFromDensity(
1890 mPadding.top, sourceDensity, targetDensity, false);
1891 mPadding.right = Drawable.scaleFromDensity(
1892 mPadding.right, sourceDensity, targetDensity, false);
1893 mPadding.bottom = Drawable.scaleFromDensity(
1894 mPadding.bottom, sourceDensity, targetDensity, false);
1897 mRadius = Drawable.scaleFromDensity(mRadius, sourceDensity, targetDensity);
1899 if (mRadiusArray != null) {
1900 mRadiusArray[0] = Drawable.scaleFromDensity(
1901 (int) mRadiusArray[0], sourceDensity, targetDensity, true);
1902 mRadiusArray[1] = Drawable.scaleFromDensity(
1903 (int) mRadiusArray[1], sourceDensity, targetDensity, true);
1904 mRadiusArray[2] = Drawable.scaleFromDensity(
1905 (int) mRadiusArray[2], sourceDensity, targetDensity, true);
1906 mRadiusArray[3] = Drawable.scaleFromDensity(
1907 (int) mRadiusArray[3], sourceDensity, targetDensity, true);
1909 if (mStrokeWidth > 0) {
1910 mStrokeWidth = Drawable.scaleFromDensity(
1911 mStrokeWidth, sourceDensity, targetDensity, true);
1913 if (mStrokeDashWidth > 0) {
1914 mStrokeDashWidth = Drawable.scaleFromDensity(
1915 mStrokeDashGap, sourceDensity, targetDensity);
1917 if (mStrokeDashGap > 0) {
1918 mStrokeDashGap = Drawable.scaleFromDensity(
1919 mStrokeDashGap, sourceDensity, targetDensity);
1921 if (mGradientRadiusType == RADIUS_TYPE_PIXELS) {
1922 mGradientRadius = Drawable.scaleFromDensity(
1923 mGradientRadius, sourceDensity, targetDensity);
1926 mWidth = Drawable.scaleFromDensity(mWidth, sourceDensity, targetDensity, true);
1929 mHeight = Drawable.scaleFromDensity(mHeight, sourceDensity, targetDensity, true);
1934 public boolean canApplyTheme() {
1935 return mThemeAttrs != null
1936 || mAttrSize != null || mAttrGradient != null
1937 || mAttrSolid != null || mAttrStroke != null
1938 || mAttrCorners != null || mAttrPadding != null
1939 || (mTint != null && mTint.canApplyTheme())
1940 || (mStrokeColors != null && mStrokeColors.canApplyTheme())
1941 || (mSolidColors != null && mSolidColors.canApplyTheme())
1942 || super.canApplyTheme();
1946 public Drawable newDrawable() {
1947 return new GradientDrawable(this, null);
1951 public Drawable newDrawable(@Nullable Resources res) {
1952 // If this drawable is being created for a different density,
1953 // just create a new constant state and call it a day.
1954 final GradientState state;
1955 final int density = Drawable.resolveDensity(res, mDensity);
1956 if (density != mDensity) {
1957 state = new GradientState(this, res);
1962 return new GradientDrawable(state, res);
1966 public @Config int getChangingConfigurations() {
1967 return mChangingConfigurations
1968 | (mStrokeColors != null ? mStrokeColors.getChangingConfigurations() : 0)
1969 | (mSolidColors != null ? mSolidColors.getChangingConfigurations() : 0)
1970 | (mTint != null ? mTint.getChangingConfigurations() : 0);
1973 public void setShape(@Shape int shape) {
1978 public void setGradientType(@GradientType int gradient) {
1979 mGradient = gradient;
1982 public void setGradientCenter(float x, float y) {
1987 public void setGradientColors(@Nullable int[] colors) {
1988 mGradientColors = colors;
1989 mSolidColors = null;
1993 public void setSolidColors(@Nullable ColorStateList colors) {
1994 mGradientColors = null;
1995 mSolidColors = colors;
1999 private void computeOpacity() {
2000 mOpaqueOverBounds = false;
2001 mOpaqueOverShape = false;
2003 if (mGradientColors != null) {
2004 for (int i = 0; i < mGradientColors.length; i++) {
2005 if (!isOpaque(mGradientColors[i])) {
2011 // An unfilled shape is not opaque over bounds or shape
2012 if (mGradientColors == null && mSolidColors == null) {
2016 // Colors are opaque, so opaqueOverShape=true,
2017 mOpaqueOverShape = true;
2018 // and opaqueOverBounds=true if shape fills bounds
2019 mOpaqueOverBounds = mShape == RECTANGLE
2021 && mRadiusArray == null;
2024 public void setStroke(int width, @Nullable ColorStateList colors, float dashWidth,
2026 mStrokeWidth = width;
2027 mStrokeColors = colors;
2028 mStrokeDashWidth = dashWidth;
2029 mStrokeDashGap = dashGap;
2033 public void setCornerRadius(float radius) {
2038 mRadiusArray = null;
2041 public void setCornerRadii(float[] radii) {
2042 mRadiusArray = radii;
2043 if (radii == null) {
2048 public void setSize(int width, int height) {
2053 public void setGradientRadius(float gradientRadius, @RadiusType int type) {
2054 mGradientRadius = gradientRadius;
2055 mGradientRadiusType = type;
2059 static boolean isOpaque(int color) {
2060 return ((color >> 24) & 0xff) == 0xff;
2064 * Creates a new themed GradientDrawable based on the specified constant state.
2066 * The resulting drawable is guaranteed to have a new constant state.
2068 * @param state Constant state from which the drawable inherits
2070 private GradientDrawable(@NonNull GradientState state, @Nullable Resources res) {
2071 mGradientState = state;
2073 updateLocalState(res);
2076 private void updateLocalState(Resources res) {
2077 final GradientState state = mGradientState;
2079 if (state.mSolidColors != null) {
2080 final int[] currentState = getState();
2081 final int stateColor = state.mSolidColors.getColorForState(currentState, 0);
2082 mFillPaint.setColor(stateColor);
2083 } else if (state.mGradientColors == null) {
2084 // If we don't have a solid color and we don't have a gradient,
2085 // the app is stroking the shape, set the color to the default
2086 // value of state.mSolidColor
2087 mFillPaint.setColor(0);
2089 // Otherwise, make sure the fill alpha is maxed out.
2090 mFillPaint.setColor(Color.BLACK);
2093 mPadding = state.mPadding;
2095 if (state.mStrokeWidth >= 0) {
2096 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
2097 mStrokePaint.setStyle(Paint.Style.STROKE);
2098 mStrokePaint.setStrokeWidth(state.mStrokeWidth);
2100 if (state.mStrokeColors != null) {
2101 final int[] currentState = getState();
2102 final int strokeStateColor = state.mStrokeColors.getColorForState(
2104 mStrokePaint.setColor(strokeStateColor);
2107 if (state.mStrokeDashWidth != 0.0f) {
2108 final DashPathEffect e = new DashPathEffect(
2109 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
2110 mStrokePaint.setPathEffect(e);
2114 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
2115 mGradientIsDirty = true;
2117 state.computeOpacity();