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.graphics.*; 20import android.graphics.drawable.shapes.Shape; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.util.AttributeSet; 24 25import org.xmlpull.v1.XmlPullParser; 26import org.xmlpull.v1.XmlPullParserException; 27 28import java.io.IOException; 29 30/** 31 * A Drawable object that draws primitive shapes. 32 * A ShapeDrawable takes a {@link android.graphics.drawable.shapes.Shape} 33 * object and manages its presence on the screen. If no Shape is given, then 34 * the ShapeDrawable will default to a 35 * {@link android.graphics.drawable.shapes.RectShape}. 36 * 37 * <p>This object can be defined in an XML file with the <code><shape></code> element.</p> 38 * 39 * <div class="special reference"> 40 * <h3>Developer Guides</h3> 41 * <p>For more information about how to use ShapeDrawable, read the 42 * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable"> 43 * Canvas and Drawables</a> document. For more information about defining a ShapeDrawable in 44 * XML, read the 45 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape">Drawable Resources</a> 46 * document.</p></div> 47 * 48 * @attr ref android.R.styleable#ShapeDrawablePadding_left 49 * @attr ref android.R.styleable#ShapeDrawablePadding_top 50 * @attr ref android.R.styleable#ShapeDrawablePadding_right 51 * @attr ref android.R.styleable#ShapeDrawablePadding_bottom 52 * @attr ref android.R.styleable#ShapeDrawable_color 53 * @attr ref android.R.styleable#ShapeDrawable_width 54 * @attr ref android.R.styleable#ShapeDrawable_height 55 */ 56public class ShapeDrawable extends Drawable { 57 private ShapeState mShapeState; 58 private boolean mMutated; 59 60 /** 61 * ShapeDrawable constructor. 62 */ 63 public ShapeDrawable() { 64 this((ShapeState) null); 65 } 66 67 /** 68 * Creates a ShapeDrawable with a specified Shape. 69 * 70 * @param s the Shape that this ShapeDrawable should be 71 */ 72 public ShapeDrawable(Shape s) { 73 this((ShapeState) null); 74 75 mShapeState.mShape = s; 76 } 77 78 private ShapeDrawable(ShapeState state) { 79 mShapeState = new ShapeState(state); 80 } 81 82 /** 83 * Returns the Shape of this ShapeDrawable. 84 */ 85 public Shape getShape() { 86 return mShapeState.mShape; 87 } 88 89 /** 90 * Sets the Shape of this ShapeDrawable. 91 */ 92 public void setShape(Shape s) { 93 mShapeState.mShape = s; 94 updateShape(); 95 } 96 97 /** 98 * Sets a ShaderFactory to which requests for a 99 * {@link android.graphics.Shader} object will be made. 100 * 101 * @param fact an instance of your ShaderFactory implementation 102 */ 103 public void setShaderFactory(ShaderFactory fact) { 104 mShapeState.mShaderFactory = fact; 105 } 106 107 /** 108 * Returns the ShaderFactory used by this ShapeDrawable for requesting a 109 * {@link android.graphics.Shader}. 110 */ 111 public ShaderFactory getShaderFactory() { 112 return mShapeState.mShaderFactory; 113 } 114 115 /** 116 * Returns the Paint used to draw the shape. 117 */ 118 public Paint getPaint() { 119 return mShapeState.mPaint; 120 } 121 122 /** 123 * Sets padding for the shape. 124 * 125 * @param left padding for the left side (in pixels) 126 * @param top padding for the top (in pixels) 127 * @param right padding for the right side (in pixels) 128 * @param bottom padding for the bottom (in pixels) 129 */ 130 public void setPadding(int left, int top, int right, int bottom) { 131 if ((left | top | right | bottom) == 0) { 132 mShapeState.mPadding = null; 133 } else { 134 if (mShapeState.mPadding == null) { 135 mShapeState.mPadding = new Rect(); 136 } 137 mShapeState.mPadding.set(left, top, right, bottom); 138 } 139 invalidateSelf(); 140 } 141 142 /** 143 * Sets padding for this shape, defined by a Rect object. 144 * Define the padding in the Rect object as: left, top, right, bottom. 145 */ 146 public void setPadding(Rect padding) { 147 if (padding == null) { 148 mShapeState.mPadding = null; 149 } else { 150 if (mShapeState.mPadding == null) { 151 mShapeState.mPadding = new Rect(); 152 } 153 mShapeState.mPadding.set(padding); 154 } 155 invalidateSelf(); 156 } 157 158 /** 159 * Sets the intrinsic (default) width for this shape. 160 * 161 * @param width the intrinsic width (in pixels) 162 */ 163 public void setIntrinsicWidth(int width) { 164 mShapeState.mIntrinsicWidth = width; 165 invalidateSelf(); 166 } 167 168 /** 169 * Sets the intrinsic (default) height for this shape. 170 * 171 * @param height the intrinsic height (in pixels) 172 */ 173 public void setIntrinsicHeight(int height) { 174 mShapeState.mIntrinsicHeight = height; 175 invalidateSelf(); 176 } 177 178 @Override 179 public int getIntrinsicWidth() { 180 return mShapeState.mIntrinsicWidth; 181 } 182 183 @Override 184 public int getIntrinsicHeight() { 185 return mShapeState.mIntrinsicHeight; 186 } 187 188 @Override 189 public boolean getPadding(Rect padding) { 190 if (mShapeState.mPadding != null) { 191 padding.set(mShapeState.mPadding); 192 return true; 193 } else { 194 return super.getPadding(padding); 195 } 196 } 197 198 private static int modulateAlpha(int paintAlpha, int alpha) { 199 int scale = alpha + (alpha >>> 7); // convert to 0..256 200 return paintAlpha * scale >>> 8; 201 } 202 203 /** 204 * Called from the drawable's draw() method after the canvas has been set 205 * to draw the shape at (0,0). Subclasses can override for special effects 206 * such as multiple layers, stroking, etc. 207 */ 208 protected void onDraw(Shape shape, Canvas canvas, Paint paint) { 209 shape.draw(canvas, paint); 210 } 211 212 @Override 213 public void draw(Canvas canvas) { 214 Rect r = getBounds(); 215 Paint paint = mShapeState.mPaint; 216 217 int prevAlpha = paint.getAlpha(); 218 paint.setAlpha(modulateAlpha(prevAlpha, mShapeState.mAlpha)); 219 220 // only draw shape if it may affect output 221 if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadow) { 222 if (mShapeState.mShape != null) { 223 // need the save both for the translate, and for the (unknown) Shape 224 int count = canvas.save(); 225 canvas.translate(r.left, r.top); 226 onDraw(mShapeState.mShape, canvas, paint); 227 canvas.restoreToCount(count); 228 } else { 229 canvas.drawRect(r, paint); 230 } 231 } 232 233 // restore 234 paint.setAlpha(prevAlpha); 235 } 236 237 @Override 238 public int getChangingConfigurations() { 239 return super.getChangingConfigurations() 240 | mShapeState.mChangingConfigurations; 241 } 242 243 /** 244 * Set the alpha level for this drawable [0..255]. Note that this drawable 245 * also has a color in its paint, which has an alpha as well. These two 246 * values are automatically combined during drawing. Thus if the color's 247 * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then 248 * the combined alpha that will be used during drawing will be 37.5% 249 * (i.e. 96). 250 */ 251 @Override public void setAlpha(int alpha) { 252 mShapeState.mAlpha = alpha; 253 invalidateSelf(); 254 } 255 256 @Override 257 public int getAlpha() { 258 return mShapeState.mAlpha; 259 } 260 261 @Override 262 public void setColorFilter(ColorFilter cf) { 263 mShapeState.mPaint.setColorFilter(cf); 264 invalidateSelf(); 265 } 266 267 @Override 268 public int getOpacity() { 269 if (mShapeState.mShape == null) { 270 final Paint p = mShapeState.mPaint; 271 if (p.getXfermode() == null) { 272 final int alpha = p.getAlpha(); 273 if (alpha == 0) { 274 return PixelFormat.TRANSPARENT; 275 } 276 if (alpha == 255) { 277 return PixelFormat.OPAQUE; 278 } 279 } 280 } 281 // not sure, so be safe 282 return PixelFormat.TRANSLUCENT; 283 } 284 285 @Override 286 public void setDither(boolean dither) { 287 mShapeState.mPaint.setDither(dither); 288 invalidateSelf(); 289 } 290 291 @Override 292 protected void onBoundsChange(Rect bounds) { 293 super.onBoundsChange(bounds); 294 updateShape(); 295 } 296 297 /** 298 * Subclasses override this to parse custom subelements. 299 * If you handle it, return true, else return <em>super.inflateTag(...)</em>. 300 */ 301 protected boolean inflateTag(String name, Resources r, XmlPullParser parser, 302 AttributeSet attrs) { 303 304 if ("padding".equals(name)) { 305 TypedArray a = r.obtainAttributes(attrs, 306 com.android.internal.R.styleable.ShapeDrawablePadding); 307 setPadding( 308 a.getDimensionPixelOffset( 309 com.android.internal.R.styleable.ShapeDrawablePadding_left, 0), 310 a.getDimensionPixelOffset( 311 com.android.internal.R.styleable.ShapeDrawablePadding_top, 0), 312 a.getDimensionPixelOffset( 313 com.android.internal.R.styleable.ShapeDrawablePadding_right, 0), 314 a.getDimensionPixelOffset( 315 com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0)); 316 a.recycle(); 317 return true; 318 } 319 320 return false; 321 } 322 323 @Override 324 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 325 throws XmlPullParserException, IOException { 326 super.inflate(r, parser, attrs); 327 328 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ShapeDrawable); 329 330 int color = mShapeState.mPaint.getColor(); 331 color = a.getColor(com.android.internal.R.styleable.ShapeDrawable_color, color); 332 mShapeState.mPaint.setColor(color); 333 334 boolean dither = a.getBoolean(com.android.internal.R.styleable.ShapeDrawable_dither, false); 335 mShapeState.mPaint.setDither(dither); 336 337 setIntrinsicWidth((int) 338 a.getDimension(com.android.internal.R.styleable.ShapeDrawable_width, 0f)); 339 setIntrinsicHeight((int) 340 a.getDimension(com.android.internal.R.styleable.ShapeDrawable_height, 0f)); 341 342 a.recycle(); 343 344 int type; 345 final int outerDepth = parser.getDepth(); 346 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 347 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 348 if (type != XmlPullParser.START_TAG) { 349 continue; 350 } 351 352 final String name = parser.getName(); 353 // call our subclass 354 if (!inflateTag(name, r, parser, attrs)) { 355 android.util.Log.w("drawable", "Unknown element: " + name + 356 " for ShapeDrawable " + this); 357 } 358 } 359 } 360 361 private void updateShape() { 362 if (mShapeState.mShape != null) { 363 final Rect r = getBounds(); 364 final int w = r.width(); 365 final int h = r.height(); 366 367 mShapeState.mShape.resize(w, h); 368 if (mShapeState.mShaderFactory != null) { 369 mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h)); 370 } 371 } 372 invalidateSelf(); 373 } 374 375 @Override 376 public ConstantState getConstantState() { 377 mShapeState.mChangingConfigurations = getChangingConfigurations(); 378 return mShapeState; 379 } 380 381 @Override 382 public Drawable mutate() { 383 if (!mMutated && super.mutate() == this) { 384 if (mShapeState.mPaint != null) { 385 mShapeState.mPaint = new Paint(mShapeState.mPaint); 386 } else { 387 mShapeState.mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 388 } 389 if (mShapeState.mPadding != null) { 390 mShapeState.mPadding = new Rect(mShapeState.mPadding); 391 } else { 392 mShapeState.mPadding = new Rect(); 393 } 394 try { 395 mShapeState.mShape = mShapeState.mShape.clone(); 396 } catch (CloneNotSupportedException e) { 397 return null; 398 } 399 mMutated = true; 400 } 401 return this; 402 } 403 404 /** 405 * Defines the intrinsic properties of this ShapeDrawable's Shape. 406 */ 407 final static class ShapeState extends ConstantState { 408 int mChangingConfigurations; 409 Paint mPaint; 410 Shape mShape; 411 Rect mPadding; 412 int mIntrinsicWidth; 413 int mIntrinsicHeight; 414 int mAlpha = 255; 415 ShaderFactory mShaderFactory; 416 417 ShapeState(ShapeState orig) { 418 if (orig != null) { 419 mPaint = orig.mPaint; 420 mShape = orig.mShape; 421 mPadding = orig.mPadding; 422 mIntrinsicWidth = orig.mIntrinsicWidth; 423 mIntrinsicHeight = orig.mIntrinsicHeight; 424 mAlpha = orig.mAlpha; 425 mShaderFactory = orig.mShaderFactory; 426 } else { 427 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 428 } 429 } 430 431 @Override 432 public Drawable newDrawable() { 433 return new ShapeDrawable(this); 434 } 435 436 @Override 437 public Drawable newDrawable(Resources res) { 438 return new ShapeDrawable(this); 439 } 440 441 @Override 442 public int getChangingConfigurations() { 443 return mChangingConfigurations; 444 } 445 } 446 447 /** 448 * Base class defines a factory object that is called each time the drawable 449 * is resized (has a new width or height). Its resize() method returns a 450 * corresponding shader, or null. 451 * Implement this class if you'd like your ShapeDrawable to use a special 452 * {@link android.graphics.Shader}, such as a 453 * {@link android.graphics.LinearGradient}. 454 * 455 */ 456 public static abstract class ShaderFactory { 457 /** 458 * Returns the Shader to be drawn when a Drawable is drawn. 459 * The dimensions of the Drawable are passed because they may be needed 460 * to adjust how the Shader is configured for drawing. 461 * This is called by ShapeDrawable.setShape(). 462 * 463 * @param width the width of the Drawable being drawn 464 * @param height the heigh of the Drawable being drawn 465 * @return the Shader to be drawn 466 */ 467 public abstract Shader resize(int width, int height); 468 } 469 470 // other subclass could wack the Shader's localmatrix based on the 471 // resize params (e.g. scaletofit, etc.). This could be used to scale 472 // a bitmap to fill the bounds without needing any other special casing. 473} 474 475