Home | History | Annotate | Download | only in drawable
      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 <a href="{@docRoot}
     57  * guide/topics/resources/drawable-resource.html#Shape">Drawable Resources</a>
     58  * 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      */
     78     public ShapeDrawable() {
     79         this(new ShapeState(null), 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, 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()
    265                 | mShapeState.mChangingConfigurations;
    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
    277     public void setAlpha(int alpha) {
    278         mShapeState.mAlpha = alpha;
    279         invalidateSelf();
    280     }
    281 
    282     @Override
    283     public int getAlpha() {
    284         return mShapeState.mAlpha;
    285     }
    286 
    287     @Override
    288     public void setTintList(ColorStateList tint) {
    289         mShapeState.mTint = tint;
    290         mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode);
    291         invalidateSelf();
    292     }
    293 
    294     @Override
    295     public void setTintMode(PorterDuff.Mode tintMode) {
    296         mShapeState.mTintMode = tintMode;
    297         mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode);
    298         invalidateSelf();
    299     }
    300 
    301     @Override
    302     public void setColorFilter(ColorFilter cf) {
    303         mShapeState.mPaint.setColorFilter(cf);
    304         invalidateSelf();
    305     }
    306 
    307     @Override
    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
    326     public void setDither(boolean dither) {
    327         mShapeState.mPaint.setDither(dither);
    328         invalidateSelf();
    329     }
    330 
    331     @Override
    332     protected void onBoundsChange(Rect bounds) {
    333         super.onBoundsChange(bounds);
    334         updateShape();
    335     }
    336 
    337     @Override
    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
    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      */
    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
    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         initializeWithState(mShapeState, r);
    406     }
    407 
    408     @Override
    409     public void applyTheme(Theme t) {
    410         super.applyTheme(t);
    411 
    412         final ShapeState state = mShapeState;
    413         if (state == null || state.mThemeAttrs == null) {
    414             return;
    415         }
    416 
    417         final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable);
    418         updateStateFromTypedArray(a);
    419         a.recycle();
    420 
    421         // Update local properties.
    422         initializeWithState(state, t.getResources());
    423     }
    424 
    425     private void updateStateFromTypedArray(TypedArray a) {
    426         final ShapeState state = mShapeState;
    427         final Paint paint = state.mPaint;
    428 
    429         // Account for any configuration changes.
    430         state.mChangingConfigurations |= a.getChangingConfigurations();
    431 
    432         // Extract the theme attributes, if any.
    433         state.mThemeAttrs = a.extractThemeAttrs();
    434 
    435         int color = paint.getColor();
    436         color = a.getColor(R.styleable.ShapeDrawable_color, color);
    437         paint.setColor(color);
    438 
    439         boolean dither = paint.isDither();
    440         dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither);
    441         paint.setDither(dither);
    442 
    443         setIntrinsicWidth((int) a.getDimension(
    444                 R.styleable.ShapeDrawable_width, state.mIntrinsicWidth));
    445         setIntrinsicHeight((int) a.getDimension(
    446                 R.styleable.ShapeDrawable_height, state.mIntrinsicHeight));
    447 
    448         final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1);
    449         if (tintMode != -1) {
    450             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
    451         }
    452 
    453         final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint);
    454         if (tint != null) {
    455             state.mTint = tint;
    456         }
    457     }
    458 
    459     private void updateShape() {
    460         if (mShapeState.mShape != null) {
    461             final Rect r = getBounds();
    462             final int w = r.width();
    463             final int h = r.height();
    464 
    465             mShapeState.mShape.resize(w, h);
    466             if (mShapeState.mShaderFactory != null) {
    467                 mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h));
    468             }
    469         }
    470         invalidateSelf();
    471     }
    472 
    473     @Override
    474     public void getOutline(Outline outline) {
    475         if (mShapeState.mShape != null) {
    476             mShapeState.mShape.getOutline(outline);
    477             outline.setAlpha(getAlpha() / 255.0f);
    478         }
    479     }
    480 
    481     @Override
    482     public ConstantState getConstantState() {
    483         mShapeState.mChangingConfigurations = getChangingConfigurations();
    484         return mShapeState;
    485     }
    486 
    487     @Override
    488     public Drawable mutate() {
    489         if (!mMutated && super.mutate() == this) {
    490             if (mShapeState.mPaint != null) {
    491                 mShapeState.mPaint = new Paint(mShapeState.mPaint);
    492             } else {
    493                 mShapeState.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    494             }
    495             if (mShapeState.mPadding != null) {
    496                 mShapeState.mPadding = new Rect(mShapeState.mPadding);
    497             } else {
    498                 mShapeState.mPadding = new Rect();
    499             }
    500             try {
    501                 mShapeState.mShape = mShapeState.mShape.clone();
    502             } catch (CloneNotSupportedException e) {
    503                 return null;
    504             }
    505             mMutated = true;
    506         }
    507         return this;
    508     }
    509 
    510     /**
    511      * Defines the intrinsic properties of this ShapeDrawable's Shape.
    512      */
    513     final static class ShapeState extends ConstantState {
    514         int[] mThemeAttrs;
    515         int mChangingConfigurations;
    516         Paint mPaint;
    517         Shape mShape;
    518         ColorStateList mTint = null;
    519         Mode mTintMode = DEFAULT_TINT_MODE;
    520         Rect mPadding;
    521         int mIntrinsicWidth;
    522         int mIntrinsicHeight;
    523         int mAlpha = 255;
    524         ShaderFactory mShaderFactory;
    525 
    526         ShapeState(ShapeState orig) {
    527             if (orig != null) {
    528                 mThemeAttrs = orig.mThemeAttrs;
    529                 mPaint = orig.mPaint;
    530                 mShape = orig.mShape;
    531                 mTint = orig.mTint;
    532                 mTintMode = orig.mTintMode;
    533                 mPadding = orig.mPadding;
    534                 mIntrinsicWidth = orig.mIntrinsicWidth;
    535                 mIntrinsicHeight = orig.mIntrinsicHeight;
    536                 mAlpha = orig.mAlpha;
    537                 mShaderFactory = orig.mShaderFactory;
    538             } else {
    539                 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    540             }
    541         }
    542 
    543         @Override
    544         public boolean canApplyTheme() {
    545             return mThemeAttrs != null;
    546         }
    547 
    548         @Override
    549         public Drawable newDrawable() {
    550             return new ShapeDrawable(this, null, null);
    551         }
    552 
    553         @Override
    554         public Drawable newDrawable(Resources res) {
    555             return new ShapeDrawable(this, res, null);
    556         }
    557 
    558         @Override
    559         public Drawable newDrawable(Resources res, Theme theme) {
    560             return new ShapeDrawable(this, res, theme);
    561         }
    562 
    563         @Override
    564         public int getChangingConfigurations() {
    565             return mChangingConfigurations;
    566         }
    567     }
    568 
    569     /**
    570      * The one constructor to rule them all. This is called by all public
    571      * constructors to set the state and initialize local properties.
    572      */
    573     private ShapeDrawable(ShapeState state, Resources res, Theme theme) {
    574         if (theme != null && state.canApplyTheme()) {
    575             mShapeState = new ShapeState(state);
    576             applyTheme(theme);
    577         } else {
    578             mShapeState = state;
    579         }
    580 
    581         initializeWithState(state, res);
    582     }
    583 
    584     /**
    585      * Initializes local dynamic properties from state. This should be called
    586      * after significant state changes, e.g. from the One True Constructor and
    587      * after inflating or applying a theme.
    588      */
    589     private void initializeWithState(ShapeState state, Resources res) {
    590         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
    591     }
    592 
    593     /**
    594      * Base class defines a factory object that is called each time the drawable
    595      * is resized (has a new width or height). Its resize() method returns a
    596      * corresponding shader, or null. Implement this class if you'd like your
    597      * ShapeDrawable to use a special {@link android.graphics.Shader}, such as a
    598      * {@link android.graphics.LinearGradient}.
    599      */
    600     public static abstract class ShaderFactory {
    601         /**
    602          * Returns the Shader to be drawn when a Drawable is drawn. The
    603          * dimensions of the Drawable are passed because they may be needed to
    604          * adjust how the Shader is configured for drawing. This is called by
    605          * ShapeDrawable.setShape().
    606          *
    607          * @param width the width of the Drawable being drawn
    608          * @param height the heigh of the Drawable being drawn
    609          * @return the Shader to be drawn
    610          */
    611         public abstract Shader resize(int width, int height);
    612     }
    613 
    614     // other subclass could wack the Shader's localmatrix based on the
    615     // resize params (e.g. scaletofit, etc.). This could be used to scale
    616     // a bitmap to fill the bounds without needing any other special casing.
    617 }
    618