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