ShapeDrawable.java revision 7ed1431c83286abc83b9e5afc45fbd21ecb777b1
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
17package android.graphics.drawable;
18
19import android.content.res.ColorStateList;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.graphics.Canvas;
23import android.graphics.ColorFilter;
24import android.graphics.Outline;
25import android.graphics.Paint;
26import android.graphics.PixelFormat;
27import android.graphics.PorterDuff;
28import android.graphics.PorterDuff.Mode;
29import android.graphics.PorterDuffColorFilter;
30import android.graphics.Rect;
31import android.graphics.Shader;
32import android.graphics.drawable.shapes.Shape;
33import android.content.res.Resources.Theme;
34import android.util.AttributeSet;
35
36import com.android.internal.R;
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import 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 */
70public class ShapeDrawable extends Drawable {
71    private ShapeState mShapeState;
72    private PorterDuffColorFilter mTintFilter;
73    private boolean mMutated;
74
75    /**
76     * ShapeDrawable constructor.
77     */
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     */
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     */
96    public Shape getShape() {
97        return mShapeState.mShape;
98    }
99
100    /**
101     * Sets the Shape of this ShapeDrawable.
102     */
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     */
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     */
122    public ShaderFactory getShaderFactory() {
123        return mShapeState.mShaderFactory;
124    }
125
126    /**
127     * Returns the Paint used to draw the shape.
128     */
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     */
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     */
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     */
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     */
184    public void setIntrinsicHeight(int height) {
185        mShapeState.mIntrinsicHeight = height;
186        invalidateSelf();
187    }
188
189    @Override
190    public int getIntrinsicWidth() {
191        return mShapeState.mIntrinsicWidth;
192    }
193
194    @Override
195    public int getIntrinsicHeight() {
196        return mShapeState.mIntrinsicHeight;
197    }
198
199    @Override
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
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     */
219    protected void onDraw(Shape shape, Canvas canvas, Paint paint) {
220        shape.draw(canvas, paint);
221    }
222
223    @Override
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
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
276    public void setAlpha(int alpha) {
277        mShapeState.mAlpha = alpha;
278        invalidateSelf();
279    }
280
281    @Override
282    public int getAlpha() {
283        return mShapeState.mAlpha;
284    }
285
286    @Override
287    public void setTintList(ColorStateList tint) {
288        mShapeState.mTint = tint;
289        mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode);
290        invalidateSelf();
291    }
292
293    @Override
294    public void setTintMode(PorterDuff.Mode tintMode) {
295        mShapeState.mTintMode = tintMode;
296        mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode);
297        invalidateSelf();
298    }
299
300    @Override
301    public void setColorFilter(ColorFilter colorFilter) {
302        mShapeState.mPaint.setColorFilter(colorFilter);
303        invalidateSelf();
304    }
305
306    @Override
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
325    public void setDither(boolean dither) {
326        mShapeState.mPaint.setDither(dither);
327        invalidateSelf();
328    }
329
330    @Override
331    public boolean getDither() {
332        return mShapeState.mPaint.isDither();
333    }
334
335    @Override
336    protected void onBoundsChange(Rect bounds) {
337        super.onBoundsChange(bounds);
338        updateShape();
339    }
340
341    @Override
342    protected boolean onStateChange(int[] stateSet) {
343        final ShapeState state = mShapeState;
344        if (state.mTint != null && state.mTintMode != null) {
345            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
346            return true;
347        }
348        return false;
349    }
350
351    @Override
352    public boolean isStateful() {
353        final ShapeState s = mShapeState;
354        return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
355    }
356
357    /**
358     * Subclasses override this to parse custom subelements. If you handle it,
359     * return true, else return <em>super.inflateTag(...)</em>.
360     */
361    protected boolean inflateTag(String name, Resources r, XmlPullParser parser,
362            AttributeSet attrs) {
363
364        if ("padding".equals(name)) {
365            TypedArray a = r.obtainAttributes(attrs,
366                    com.android.internal.R.styleable.ShapeDrawablePadding);
367            setPadding(
368                    a.getDimensionPixelOffset(
369                            com.android.internal.R.styleable.ShapeDrawablePadding_left, 0),
370                    a.getDimensionPixelOffset(
371                            com.android.internal.R.styleable.ShapeDrawablePadding_top, 0),
372                    a.getDimensionPixelOffset(
373                            com.android.internal.R.styleable.ShapeDrawablePadding_right, 0),
374                    a.getDimensionPixelOffset(
375                            com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0));
376            a.recycle();
377            return true;
378        }
379
380        return false;
381    }
382
383    @Override
384    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
385            throws XmlPullParserException, IOException {
386        super.inflate(r, parser, attrs, theme);
387
388        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable);
389        updateStateFromTypedArray(a);
390        a.recycle();
391
392        int type;
393        final int outerDepth = parser.getDepth();
394        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
395                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
396            if (type != XmlPullParser.START_TAG) {
397                continue;
398            }
399
400            final String name = parser.getName();
401            // call our subclass
402            if (!inflateTag(name, r, parser, attrs)) {
403                android.util.Log.w("drawable", "Unknown element: " + name +
404                        " for ShapeDrawable " + this);
405            }
406        }
407
408        // Update local properties.
409        updateLocalState(r);
410    }
411
412    @Override
413    public void applyTheme(Theme t) {
414        super.applyTheme(t);
415
416        final ShapeState state = mShapeState;
417        if (state == null) {
418            return;
419        }
420
421        if (state.mThemeAttrs != null) {
422            final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
423            updateStateFromTypedArray(a);
424            a.recycle();
425        }
426
427        // Apply theme to contained color state list.
428        if (state.mTint != null && state.mTint.canApplyTheme()) {
429            state.mTint = state.mTint.obtainForTheme(t);
430        }
431
432        // Update local properties.
433        updateLocalState(t.getResources());
434    }
435
436    private void updateStateFromTypedArray(TypedArray a) {
437        final ShapeState state = mShapeState;
438        final Paint paint = state.mPaint;
439
440        // Account for any configuration changes.
441        state.mChangingConfigurations |= a.getChangingConfigurations();
442
443        // Extract the theme attributes, if any.
444        state.mThemeAttrs = a.extractThemeAttrs();
445
446        int color = paint.getColor();
447        color = a.getColor(R.styleable.ShapeDrawable_color, color);
448        paint.setColor(color);
449
450        boolean dither = paint.isDither();
451        dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
452        paint.setDither(dither);
453
454        setIntrinsicWidth((int) a.getDimension(
455                R.styleable.ShapeDrawable_width, state.mIntrinsicWidth));
456        setIntrinsicHeight((int) a.getDimension(
457                R.styleable.ShapeDrawable_height, state.mIntrinsicHeight));
458
459        final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
460        if (tintMode != -1) {
461            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
462        }
463
464        final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
465        if (tint != null) {
466            state.mTint = tint;
467        }
468    }
469
470    private void updateShape() {
471        if (mShapeState.mShape != null) {
472            final Rect r = getBounds();
473            final int w = r.width();
474            final int h = r.height();
475
476            mShapeState.mShape.resize(w, h);
477            if (mShapeState.mShaderFactory != null) {
478                mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h));
479            }
480        }
481        invalidateSelf();
482    }
483
484    @Override
485    public void getOutline(Outline outline) {
486        if (mShapeState.mShape != null) {
487            mShapeState.mShape.getOutline(outline);
488            outline.setAlpha(getAlpha() / 255.0f);
489        }
490    }
491
492    @Override
493    public ConstantState getConstantState() {
494        mShapeState.mChangingConfigurations = getChangingConfigurations();
495        return mShapeState;
496    }
497
498    @Override
499    public Drawable mutate() {
500        if (!mMutated && super.mutate() == this) {
501            if (mShapeState.mPaint != null) {
502                mShapeState.mPaint = new Paint(mShapeState.mPaint);
503            } else {
504                mShapeState.mPaint = new Paint();
505            }
506            if (mShapeState.mPadding != null) {
507                mShapeState.mPadding = new Rect(mShapeState.mPadding);
508            } else {
509                mShapeState.mPadding = new Rect();
510            }
511            try {
512                mShapeState.mShape = mShapeState.mShape.clone();
513            } catch (CloneNotSupportedException e) {
514                return null;
515            }
516            mMutated = true;
517        }
518        return this;
519    }
520
521    /**
522     * @hide
523     */
524    public void clearMutated() {
525        super.clearMutated();
526        mMutated = false;
527    }
528
529    /**
530     * Defines the intrinsic properties of this ShapeDrawable's Shape.
531     */
532    final static class ShapeState extends ConstantState {
533        int[] mThemeAttrs;
534        int mChangingConfigurations;
535        Paint mPaint;
536        Shape mShape;
537        ColorStateList mTint = null;
538        Mode mTintMode = DEFAULT_TINT_MODE;
539        Rect mPadding;
540        int mIntrinsicWidth;
541        int mIntrinsicHeight;
542        int mAlpha = 255;
543        ShaderFactory mShaderFactory;
544
545        ShapeState(ShapeState orig) {
546            if (orig != null) {
547                mThemeAttrs = orig.mThemeAttrs;
548                mPaint = orig.mPaint;
549                mShape = orig.mShape;
550                mTint = orig.mTint;
551                mTintMode = orig.mTintMode;
552                mPadding = orig.mPadding;
553                mIntrinsicWidth = orig.mIntrinsicWidth;
554                mIntrinsicHeight = orig.mIntrinsicHeight;
555                mAlpha = orig.mAlpha;
556                mShaderFactory = orig.mShaderFactory;
557            } else {
558                mPaint = new Paint();
559            }
560        }
561
562        @Override
563        public boolean canApplyTheme() {
564            return mThemeAttrs != null
565                    || (mTint != null && mTint.canApplyTheme());
566        }
567
568        @Override
569        public Drawable newDrawable() {
570            return new ShapeDrawable(this, null);
571        }
572
573        @Override
574        public Drawable newDrawable(Resources res) {
575            return new ShapeDrawable(this, res);
576        }
577
578        @Override
579        public int getChangingConfigurations() {
580            return mChangingConfigurations
581                    | (mTint != null ? mTint.getChangingConfigurations() : 0);
582        }
583    }
584
585    /**
586     * The one constructor to rule them all. This is called by all public
587     * constructors to set the state and initialize local properties.
588     */
589    private ShapeDrawable(ShapeState state, Resources res) {
590        mShapeState = state;
591
592        updateLocalState(res);
593    }
594
595    /**
596     * Initializes local dynamic properties from state. This should be called
597     * after significant state changes, e.g. from the One True Constructor and
598     * after inflating or applying a theme.
599     */
600    private void updateLocalState(Resources res) {
601        mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, mShapeState.mTintMode);
602    }
603
604    /**
605     * Base class defines a factory object that is called each time the drawable
606     * is resized (has a new width or height). Its resize() method returns a
607     * corresponding shader, or null. Implement this class if you'd like your
608     * ShapeDrawable to use a special {@link android.graphics.Shader}, such as a
609     * {@link android.graphics.LinearGradient}.
610     */
611    public static abstract class ShaderFactory {
612        /**
613         * Returns the Shader to be drawn when a Drawable is drawn. The
614         * dimensions of the Drawable are passed because they may be needed to
615         * adjust how the Shader is configured for drawing. This is called by
616         * ShapeDrawable.setShape().
617         *
618         * @param width the width of the Drawable being drawn
619         * @param height the heigh of the Drawable being drawn
620         * @return the Shader to be drawn
621         */
622        public abstract Shader resize(int width, int height);
623    }
624
625    // other subclass could wack the Shader's localmatrix based on the
626    // resize params (e.g. scaletofit, etc.). This could be used to scale
627    // a bitmap to fill the bounds without needing any other special casing.
628}
629