• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 
17 package android.graphics.drawable;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.TestApi;
22 import android.content.pm.ActivityInfo.Config;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.content.res.Resources.Theme;
26 import android.content.res.TypedArray;
27 import android.graphics.BlendMode;
28 import android.graphics.BlendModeColorFilter;
29 import android.graphics.Canvas;
30 import android.graphics.ColorFilter;
31 import android.graphics.Outline;
32 import android.graphics.Paint;
33 import android.graphics.PixelFormat;
34 import android.graphics.Rect;
35 import android.graphics.Shader;
36 import android.graphics.Xfermode;
37 import android.graphics.drawable.shapes.Shape;
38 import android.util.AttributeSet;
39 
40 import com.android.internal.R;
41 
42 import org.xmlpull.v1.XmlPullParser;
43 import org.xmlpull.v1.XmlPullParserException;
44 
45 import java.io.IOException;
46 
47 /**
48  * A Drawable object that draws primitive shapes. A ShapeDrawable takes a
49  * {@link android.graphics.drawable.shapes.Shape} object and manages its
50  * presence on the screen. If no Shape is given, then the ShapeDrawable will
51  * default to a {@link android.graphics.drawable.shapes.RectShape}.
52  * <p>
53  * This object can be defined in an XML file with the <code>&lt;shape></code>
54  * element.
55  * </p>
56  * <div class="special reference"> <h3>Developer Guides</h3>
57  * <p>
58  * For more information about how to use ShapeDrawable, read the <a
59  * href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable">
60  * Canvas and Drawables</a> document. For more information about defining a
61  * ShapeDrawable in XML, read the
62  * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape">
63  * Drawable Resources</a> document.
64  * </p>
65  * </div>
66  *
67  * @attr ref android.R.styleable#ShapeDrawablePadding_left
68  * @attr ref android.R.styleable#ShapeDrawablePadding_top
69  * @attr ref android.R.styleable#ShapeDrawablePadding_right
70  * @attr ref android.R.styleable#ShapeDrawablePadding_bottom
71  * @attr ref android.R.styleable#ShapeDrawable_color
72  * @attr ref android.R.styleable#ShapeDrawable_width
73  * @attr ref android.R.styleable#ShapeDrawable_height
74  */
75 public class ShapeDrawable extends Drawable {
76     private @NonNull ShapeState mShapeState;
77     private BlendModeColorFilter mBlendModeColorFilter;
78     private boolean mMutated;
79 
80     /**
81      * ShapeDrawable constructor.
82      */
ShapeDrawable()83     public ShapeDrawable() {
84         this(new ShapeState(), null);
85     }
86 
87     /**
88      * Creates a ShapeDrawable with a specified Shape.
89      *
90      * @param s the Shape that this ShapeDrawable should be
91      */
ShapeDrawable(Shape s)92     public ShapeDrawable(Shape s) {
93         this(new ShapeState(), null);
94 
95         mShapeState.mShape = s;
96     }
97 
98     /**
99      * Returns the Shape of this ShapeDrawable.
100      */
getShape()101     public Shape getShape() {
102         return mShapeState.mShape;
103     }
104 
105     /**
106      * Sets the Shape of this ShapeDrawable.
107      */
setShape(Shape s)108     public void setShape(Shape s) {
109         mShapeState.mShape = s;
110         updateShape();
111     }
112 
113     /**
114      * Sets a ShaderFactory to which requests for a
115      * {@link android.graphics.Shader} object will be made.
116      *
117      * @param fact an instance of your ShaderFactory implementation
118      */
setShaderFactory(ShaderFactory fact)119     public void setShaderFactory(ShaderFactory fact) {
120         mShapeState.mShaderFactory = fact;
121     }
122 
123     /**
124      * Returns the ShaderFactory used by this ShapeDrawable for requesting a
125      * {@link android.graphics.Shader}.
126      */
getShaderFactory()127     public ShaderFactory getShaderFactory() {
128         return mShapeState.mShaderFactory;
129     }
130 
131     /**
132      * Returns the Paint used to draw the shape.
133      */
getPaint()134     public Paint getPaint() {
135         return mShapeState.mPaint;
136     }
137 
138     /**
139      * Sets padding for the shape.
140      *
141      * @param left padding for the left side (in pixels)
142      * @param top padding for the top (in pixels)
143      * @param right padding for the right side (in pixels)
144      * @param bottom padding for the bottom (in pixels)
145      */
setPadding(int left, int top, int right, int bottom)146     public void setPadding(int left, int top, int right, int bottom) {
147         if ((left | top | right | bottom) == 0) {
148             mShapeState.mPadding = null;
149         } else {
150             if (mShapeState.mPadding == null) {
151                 mShapeState.mPadding = new Rect();
152             }
153             mShapeState.mPadding.set(left, top, right, bottom);
154         }
155         invalidateSelf();
156     }
157 
158     /**
159      * Sets padding for this shape, defined by a Rect object. Define the padding
160      * in the Rect object as: left, top, right, bottom.
161      */
setPadding(Rect padding)162     public void setPadding(Rect padding) {
163         if (padding == null) {
164             mShapeState.mPadding = null;
165         } else {
166             if (mShapeState.mPadding == null) {
167                 mShapeState.mPadding = new Rect();
168             }
169             mShapeState.mPadding.set(padding);
170         }
171         invalidateSelf();
172     }
173 
174     /**
175      * Sets the intrinsic (default) width for this shape.
176      *
177      * @param width the intrinsic width (in pixels)
178      */
setIntrinsicWidth(int width)179     public void setIntrinsicWidth(int width) {
180         mShapeState.mIntrinsicWidth = width;
181         invalidateSelf();
182     }
183 
184     /**
185      * Sets the intrinsic (default) height for this shape.
186      *
187      * @param height the intrinsic height (in pixels)
188      */
setIntrinsicHeight(int height)189     public void setIntrinsicHeight(int height) {
190         mShapeState.mIntrinsicHeight = height;
191         invalidateSelf();
192     }
193 
194     @Override
getIntrinsicWidth()195     public int getIntrinsicWidth() {
196         return mShapeState.mIntrinsicWidth;
197     }
198 
199     @Override
getIntrinsicHeight()200     public int getIntrinsicHeight() {
201         return mShapeState.mIntrinsicHeight;
202     }
203 
204     @Override
getPadding(Rect padding)205     public boolean getPadding(Rect padding) {
206         if (mShapeState.mPadding != null) {
207             padding.set(mShapeState.mPadding);
208             return true;
209         } else {
210             return super.getPadding(padding);
211         }
212     }
213 
modulateAlpha(int paintAlpha, int alpha)214     private static int modulateAlpha(int paintAlpha, int alpha) {
215         int scale = alpha + (alpha >>> 7); // convert to 0..256
216         return paintAlpha * scale >>> 8;
217     }
218 
219     /**
220      * Called from the drawable's draw() method after the canvas has been set to
221      * draw the shape at (0,0). Subclasses can override for special effects such
222      * as multiple layers, stroking, etc.
223      */
onDraw(Shape shape, Canvas canvas, Paint paint)224     protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
225         shape.draw(canvas, paint);
226     }
227 
228     @Override
draw(Canvas canvas)229     public void draw(Canvas canvas) {
230         final Rect r = getBounds();
231         final ShapeState state = mShapeState;
232         final Paint paint = state.mPaint;
233 
234         final int prevAlpha = paint.getAlpha();
235         paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha));
236 
237         // only draw shape if it may affect output
238         if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) {
239             final boolean clearColorFilter;
240             if (mBlendModeColorFilter != null && paint.getColorFilter() == null) {
241                 paint.setColorFilter(mBlendModeColorFilter);
242                 clearColorFilter = true;
243             } else {
244                 clearColorFilter = false;
245             }
246 
247             if (state.mShape != null) {
248                 // need the save both for the translate, and for the (unknown)
249                 // Shape
250                 final int count = canvas.save();
251                 canvas.translate(r.left, r.top);
252                 onDraw(state.mShape, canvas, paint);
253                 canvas.restoreToCount(count);
254             } else {
255                 canvas.drawRect(r, paint);
256             }
257 
258             if (clearColorFilter) {
259                 paint.setColorFilter(null);
260             }
261         }
262 
263         // restore
264         paint.setAlpha(prevAlpha);
265     }
266 
267     @Override
getChangingConfigurations()268     public @Config int getChangingConfigurations() {
269         return super.getChangingConfigurations() | mShapeState.getChangingConfigurations();
270     }
271 
272     /**
273      * Set the alpha level for this drawable [0..255]. Note that this drawable
274      * also has a color in its paint, which has an alpha as well. These two
275      * values are automatically combined during drawing. Thus if the color's
276      * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then
277      * the combined alpha that will be used during drawing will be 37.5% (i.e.
278      * 96).
279      */
280     @Override
setAlpha(int alpha)281     public void setAlpha(int alpha) {
282         mShapeState.mAlpha = alpha;
283         invalidateSelf();
284     }
285 
286     @Override
getAlpha()287     public int getAlpha() {
288         return mShapeState.mAlpha;
289     }
290 
291     @Override
setTintList(ColorStateList tint)292     public void setTintList(ColorStateList tint) {
293         mShapeState.mTint = tint;
294         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, tint,
295                 mShapeState.mBlendMode);
296         invalidateSelf();
297     }
298 
299     @Override
setTintBlendMode(@onNull BlendMode blendMode)300     public void setTintBlendMode(@NonNull BlendMode blendMode) {
301         mShapeState.mBlendMode = blendMode;
302         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
303                 blendMode);
304         invalidateSelf();
305     }
306 
307     @Override
setColorFilter(ColorFilter colorFilter)308     public void setColorFilter(ColorFilter colorFilter) {
309         mShapeState.mPaint.setColorFilter(colorFilter);
310         invalidateSelf();
311     }
312 
313     /**
314      * @hide
315      */
316     @Override
317     @TestApi
setXfermode(@ullable Xfermode mode)318     public void setXfermode(@Nullable Xfermode mode) {
319         mShapeState.mPaint.setXfermode(mode);
320         invalidateSelf();
321     }
322 
323     @Override
getOpacity()324     public int getOpacity() {
325         if (mShapeState.mShape == null) {
326             final Paint p = mShapeState.mPaint;
327             if (p.getXfermode() == null) {
328                 final int alpha = p.getAlpha();
329                 if (alpha == 0) {
330                     return PixelFormat.TRANSPARENT;
331                 }
332                 if (alpha == 255) {
333                     return PixelFormat.OPAQUE;
334                 }
335             }
336         }
337         // not sure, so be safe
338         return PixelFormat.TRANSLUCENT;
339     }
340 
341     @Override
setDither(boolean dither)342     public void setDither(boolean dither) {
343         mShapeState.mPaint.setDither(dither);
344         invalidateSelf();
345     }
346 
347     @Override
onBoundsChange(Rect bounds)348     protected void onBoundsChange(Rect bounds) {
349         super.onBoundsChange(bounds);
350         updateShape();
351     }
352 
353     @Override
onStateChange(int[] stateSet)354     protected boolean onStateChange(int[] stateSet) {
355         final ShapeState state = mShapeState;
356         if (state.mTint != null && state.mBlendMode != null) {
357             mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, state.mTint,
358                     state.mBlendMode);
359             return true;
360         }
361         return false;
362     }
363 
364     @Override
isStateful()365     public boolean isStateful() {
366         final ShapeState s = mShapeState;
367         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
368     }
369 
370     @Override
hasFocusStateSpecified()371     public boolean hasFocusStateSpecified() {
372         return mShapeState.mTint != null && mShapeState.mTint.hasFocusStateSpecified();
373     }
374 
375     /**
376      * Subclasses override this to parse custom subelements. If you handle it,
377      * return true, else return <em>super.inflateTag(...)</em>.
378      */
inflateTag(String name, Resources r, XmlPullParser parser, AttributeSet attrs)379     protected boolean inflateTag(String name, Resources r, XmlPullParser parser,
380             AttributeSet attrs) {
381 
382         if ("padding".equals(name)) {
383             TypedArray a = r.obtainAttributes(attrs,
384                     com.android.internal.R.styleable.ShapeDrawablePadding);
385             setPadding(
386                     a.getDimensionPixelOffset(
387                             com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
388                     a.getDimensionPixelOffset(
389                             com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
390                     a.getDimensionPixelOffset(
391                             com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
392                     a.getDimensionPixelOffset(
393                             com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
394             a.recycle();
395             return true;
396         }
397 
398         return false;
399     }
400 
401     @Override
inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)402     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
403             throws XmlPullParserException, IOException {
404         super.inflate(r, parser, attrs, theme);
405 
406         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable);
407         updateStateFromTypedArray(a);
408         a.recycle();
409 
410         int type;
411         final int outerDepth = parser.getDepth();
412         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
413                 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
414             if (type != XmlPullParser.START_TAG) {
415                 continue;
416             }
417 
418             final String name = parser.getName();
419             // call our subclass
420             if (!inflateTag(name, r, parser, attrs)) {
421                 android.util.Log.w("drawable", "Unknown element: " + name +
422                         " for ShapeDrawable " + this);
423             }
424         }
425 
426         // Update local properties.
427         updateLocalState();
428     }
429 
430     @Override
applyTheme(Theme t)431     public void applyTheme(Theme t) {
432         super.applyTheme(t);
433 
434         final ShapeState state = mShapeState;
435         if (state == null) {
436             return;
437         }
438 
439         if (state.mThemeAttrs != null) {
440             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
441             updateStateFromTypedArray(a);
442             a.recycle();
443         }
444 
445         // Apply theme to contained color state list.
446         if (state.mTint != null && state.mTint.canApplyTheme()) {
447             state.mTint = state.mTint.obtainForTheme(t);
448         }
449 
450         // Update local properties.
451         updateLocalState();
452     }
453 
updateStateFromTypedArray(TypedArray a)454     private void updateStateFromTypedArray(TypedArray a) {
455         final ShapeState state = mShapeState;
456         final Paint paint = state.mPaint;
457 
458         // Account for any configuration changes.
459         state.mChangingConfigurations |= a.getChangingConfigurations();
460 
461         // Extract the theme attributes, if any.
462         state.mThemeAttrs = a.extractThemeAttrs();
463 
464         int color = paint.getColor();
465         color = a.getColor(R.styleable.ShapeDrawable_color, color);
466         paint.setColor(color);
467 
468         boolean dither = paint.isDither();
469         dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
470         paint.setDither(dither);
471 
472         state.mIntrinsicWidth = (int) a.getDimension(
473                 R.styleable.ShapeDrawable_width, state.mIntrinsicWidth);
474         state.mIntrinsicHeight = (int) a.getDimension(
475                 R.styleable.ShapeDrawable_height, state.mIntrinsicHeight);
476 
477         final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
478         if (tintMode != -1) {
479             state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN);
480         }
481 
482         final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
483         if (tint != null) {
484             state.mTint = tint;
485         }
486     }
487 
updateShape()488     private void updateShape() {
489         if (mShapeState.mShape != null) {
490             final Rect r = getBounds();
491             final int w = r.width();
492             final int h = r.height();
493 
494             mShapeState.mShape.resize(w, h);
495             if (mShapeState.mShaderFactory != null) {
496                 mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h));
497             }
498         }
499         invalidateSelf();
500     }
501 
502     @Override
getOutline(Outline outline)503     public void getOutline(Outline outline) {
504         if (mShapeState.mShape != null) {
505             mShapeState.mShape.getOutline(outline);
506             outline.setAlpha(getAlpha() / 255.0f);
507         }
508     }
509 
510     @Override
getConstantState()511     public ConstantState getConstantState() {
512         mShapeState.mChangingConfigurations = getChangingConfigurations();
513         return mShapeState;
514     }
515 
516     @Override
mutate()517     public Drawable mutate() {
518         if (!mMutated && super.mutate() == this) {
519             mShapeState = new ShapeState(mShapeState);
520             updateLocalState();
521             mMutated = true;
522         }
523         return this;
524     }
525 
526     /**
527      * @hide
528      */
clearMutated()529     public void clearMutated() {
530         super.clearMutated();
531         mMutated = false;
532     }
533 
534     /**
535      * Defines the intrinsic properties of this ShapeDrawable's Shape.
536      */
537     static final class ShapeState extends ConstantState {
538         final @NonNull Paint mPaint;
539 
540         @Config int mChangingConfigurations;
541         int[] mThemeAttrs;
542         Shape mShape;
543         ColorStateList mTint;
544         BlendMode mBlendMode = DEFAULT_BLEND_MODE;
545         Rect mPadding;
546         int mIntrinsicWidth;
547         int mIntrinsicHeight;
548         int mAlpha = 255;
549         ShaderFactory mShaderFactory;
550 
551         /**
552          * Constructs a new ShapeState.
553          */
ShapeState()554         ShapeState() {
555             mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
556         }
557 
558         /**
559          * Constructs a new ShapeState that contains a deep copy of the
560          * specified ShapeState.
561          *
562          * @param orig the state to create a deep copy of
563          */
ShapeState(@onNull ShapeState orig)564         ShapeState(@NonNull ShapeState orig) {
565             mChangingConfigurations = orig.mChangingConfigurations;
566             mPaint = new Paint(orig.mPaint);
567             mThemeAttrs = orig.mThemeAttrs;
568             if (orig.mShape != null) {
569                 try {
570                     mShape = orig.mShape.clone();
571                 } catch (CloneNotSupportedException e) {
572                     // Well, at least we tried.
573                     mShape = orig.mShape;
574                 }
575             }
576             mTint = orig.mTint;
577             mBlendMode = orig.mBlendMode;
578             if (orig.mPadding != null) {
579                 mPadding = new Rect(orig.mPadding);
580             }
581             mIntrinsicWidth = orig.mIntrinsicWidth;
582             mIntrinsicHeight = orig.mIntrinsicHeight;
583             mAlpha = orig.mAlpha;
584 
585             // We don't have any way to clone a shader factory, so hopefully
586             // this class doesn't contain any local state.
587             mShaderFactory = orig.mShaderFactory;
588         }
589 
590         @Override
canApplyTheme()591         public boolean canApplyTheme() {
592             return mThemeAttrs != null
593                     || (mTint != null && mTint.canApplyTheme());
594         }
595 
596         @Override
newDrawable()597         public Drawable newDrawable() {
598             return new ShapeDrawable(new ShapeState(this), null);
599         }
600 
601         @Override
newDrawable(Resources res)602         public Drawable newDrawable(Resources res) {
603             return new ShapeDrawable(new ShapeState(this), res);
604         }
605 
606         @Override
getChangingConfigurations()607         public @Config int getChangingConfigurations() {
608             return mChangingConfigurations
609                     | (mTint != null ? mTint.getChangingConfigurations() : 0);
610         }
611     }
612 
613     /**
614      * The one constructor to rule them all. This is called by all public
615      * constructors to set the state and initialize local properties.
616      */
ShapeDrawable(ShapeState state, Resources res)617     private ShapeDrawable(ShapeState state, Resources res) {
618         mShapeState = state;
619 
620         updateLocalState();
621     }
622 
623     /**
624      * Initializes local dynamic properties from state. This should be called
625      * after significant state changes, e.g. from the One True Constructor and
626      * after inflating or applying a theme.
627      */
updateLocalState()628     private void updateLocalState() {
629         mBlendModeColorFilter = updateBlendModeFilter(mBlendModeColorFilter, mShapeState.mTint,
630                 mShapeState.mBlendMode);
631     }
632 
633     /**
634      * Base class defines a factory object that is called each time the drawable
635      * is resized (has a new width or height). Its resize() method returns a
636      * corresponding shader, or null. Implement this class if you'd like your
637      * ShapeDrawable to use a special {@link android.graphics.Shader}, such as a
638      * {@link android.graphics.LinearGradient}.
639      */
640     public static abstract class ShaderFactory {
641         /**
642          * Returns the Shader to be drawn when a Drawable is drawn. The
643          * dimensions of the Drawable are passed because they may be needed to
644          * adjust how the Shader is configured for drawing. This is called by
645          * ShapeDrawable.setShape().
646          *
647          * @param width the width of the Drawable being drawn
648          * @param height the heigh of the Drawable being drawn
649          * @return the Shader to be drawn
650          */
resize(int width, int height)651         public abstract Shader resize(int width, int height);
652     }
653 }
654