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