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