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