RotateDrawable.java revision 17cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8b
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 com.android.internal.R; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.graphics.Canvas; 25import android.graphics.ColorFilter; 26import android.graphics.Rect; 27import android.graphics.PorterDuff.Mode; 28import android.content.res.ColorStateList; 29import android.content.res.Resources; 30import android.content.res.TypedArray; 31import android.content.res.Resources.Theme; 32import android.util.TypedValue; 33import android.util.AttributeSet; 34import android.util.Log; 35 36import java.io.IOException; 37 38/** 39 * <p> 40 * A Drawable that can rotate another Drawable based on the current level value. 41 * The start and end angles of rotation can be controlled to map any circular 42 * arc to the level values range. 43 * <p> 44 * It can be defined in an XML file with the <code><rotate></code> element. 45 * For more information, see the guide to 46 * <a href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>. 47 * 48 * @attr ref android.R.styleable#RotateDrawable_visible 49 * @attr ref android.R.styleable#RotateDrawable_fromDegrees 50 * @attr ref android.R.styleable#RotateDrawable_toDegrees 51 * @attr ref android.R.styleable#RotateDrawable_pivotX 52 * @attr ref android.R.styleable#RotateDrawable_pivotY 53 * @attr ref android.R.styleable#RotateDrawable_drawable 54 */ 55public class RotateDrawable extends Drawable implements Drawable.Callback { 56 private static final float MAX_LEVEL = 10000.0f; 57 58 private final RotateState mState; 59 60 private boolean mMutated; 61 62 /** 63 * Create a new rotating drawable with an empty state. 64 */ 65 public RotateDrawable() { 66 this(null, null); 67 } 68 69 /** 70 * Create a new rotating drawable with the specified state. A copy of 71 * this state is used as the internal state for the newly created 72 * drawable. 73 * 74 * @param rotateState the state for this drawable 75 */ 76 private RotateDrawable(RotateState rotateState, Resources res) { 77 mState = new RotateState(rotateState, this, res); 78 } 79 80 @Override 81 public void draw(Canvas canvas) { 82 final RotateState st = mState; 83 final Drawable d = st.mDrawable; 84 final Rect bounds = d.getBounds(); 85 final int w = bounds.right - bounds.left; 86 final int h = bounds.bottom - bounds.top; 87 final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; 88 final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; 89 90 final int saveCount = canvas.save(); 91 canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top); 92 d.draw(canvas); 93 canvas.restoreToCount(saveCount); 94 } 95 96 /** 97 * Sets the drawable rotated by this RotateDrawable. 98 * 99 * @param drawable The drawable to rotate 100 */ 101 public void setDrawable(Drawable drawable) { 102 final Drawable oldDrawable = mState.mDrawable; 103 if (oldDrawable != drawable) { 104 if (oldDrawable != null) { 105 oldDrawable.setCallback(null); 106 } 107 mState.mDrawable = drawable; 108 if (drawable != null) { 109 drawable.setCallback(this); 110 } 111 } 112 } 113 114 /** 115 * @return The drawable rotated by this RotateDrawable 116 */ 117 public Drawable getDrawable() { 118 return mState.mDrawable; 119 } 120 121 @Override 122 public int getChangingConfigurations() { 123 return super.getChangingConfigurations() 124 | mState.mChangingConfigurations 125 | mState.mDrawable.getChangingConfigurations(); 126 } 127 128 @Override 129 public void setAlpha(int alpha) { 130 mState.mDrawable.setAlpha(alpha); 131 } 132 133 @Override 134 public int getAlpha() { 135 return mState.mDrawable.getAlpha(); 136 } 137 138 @Override 139 public void setColorFilter(ColorFilter cf) { 140 mState.mDrawable.setColorFilter(cf); 141 } 142 143 @Override 144 public void setTintList(ColorStateList tint) { 145 mState.mDrawable.setTintList(tint); 146 } 147 148 @Override 149 public void setTintMode(Mode tintMode) { 150 mState.mDrawable.setTintMode(tintMode); 151 } 152 153 @Override 154 public int getOpacity() { 155 return mState.mDrawable.getOpacity(); 156 } 157 158 /** 159 * Sets the start angle for rotation. 160 * 161 * @param fromDegrees Starting angle in degrees 162 * 163 * @see #getFromDegrees() 164 * @attr ref android.R.styleable#RotateDrawable_fromDegrees 165 */ 166 public void setFromDegrees(float fromDegrees) { 167 if (mState.mFromDegrees != fromDegrees) { 168 mState.mFromDegrees = fromDegrees; 169 invalidateSelf(); 170 } 171 } 172 173 /** 174 * @return The starting angle for rotation in degrees 175 * 176 * @see #setFromDegrees(float) 177 * @attr ref android.R.styleable#RotateDrawable_fromDegrees 178 */ 179 public float getFromDegrees() { 180 return mState.mFromDegrees; 181 } 182 183 /** 184 * Sets the end angle for rotation. 185 * 186 * @param toDegrees Ending angle in degrees 187 * 188 * @see #getToDegrees() 189 * @attr ref android.R.styleable#RotateDrawable_toDegrees 190 */ 191 public void setToDegrees(float toDegrees) { 192 if (mState.mToDegrees != toDegrees) { 193 mState.mToDegrees = toDegrees; 194 invalidateSelf(); 195 } 196 } 197 198 /** 199 * @return The ending angle for rotation in degrees 200 * 201 * @see #setToDegrees(float) 202 * @attr ref android.R.styleable#RotateDrawable_toDegrees 203 */ 204 public float getToDegrees() { 205 return mState.mToDegrees; 206 } 207 208 /** 209 * Sets the X position around which the drawable is rotated. 210 * 211 * @param pivotX X position around which to rotate. If the X pivot is 212 * relative, the position represents a fraction of the drawable 213 * width. Otherwise, the position represents an absolute value in 214 * pixels. 215 * 216 * @see #setPivotXRelative(boolean) 217 * @attr ref android.R.styleable#RotateDrawable_pivotX 218 */ 219 public void setPivotX(float pivotX) { 220 if (mState.mPivotX == pivotX) { 221 mState.mPivotX = pivotX; 222 invalidateSelf(); 223 } 224 } 225 226 /** 227 * @return X position around which to rotate 228 * 229 * @see #setPivotX(float) 230 * @attr ref android.R.styleable#RotateDrawable_pivotX 231 */ 232 public float getPivotX() { 233 return mState.mPivotX; 234 } 235 236 /** 237 * Sets whether the X pivot value represents a fraction of the drawable 238 * width or an absolute value in pixels. 239 * 240 * @param relative True if the X pivot represents a fraction of the drawable 241 * width, or false if it represents an absolute value in pixels 242 * 243 * @see #isPivotXRelative() 244 */ 245 public void setPivotXRelative(boolean relative) { 246 if (mState.mPivotXRel == relative) { 247 mState.mPivotXRel = relative; 248 invalidateSelf(); 249 } 250 } 251 252 /** 253 * @return True if the X pivot represents a fraction of the drawable width, 254 * or false if it represents an absolute value in pixels 255 * 256 * @see #setPivotXRelative(boolean) 257 */ 258 public boolean isPivotXRelative() { 259 return mState.mPivotXRel; 260 } 261 262 /** 263 * Sets the Y position around which the drawable is rotated. 264 * 265 * @param pivotY Y position around which to rotate. If the Y pivot is 266 * relative, the position represents a fraction of the drawable 267 * height. Otherwise, the position represents an absolute value 268 * in pixels. 269 * 270 * @see #getPivotY() 271 * @attr ref android.R.styleable#RotateDrawable_pivotY 272 */ 273 public void setPivotY(float pivotY) { 274 if (mState.mPivotY == pivotY) { 275 mState.mPivotY = pivotY; 276 invalidateSelf(); 277 } 278 } 279 280 /** 281 * @return Y position around which to rotate 282 * 283 * @see #setPivotY(float) 284 * @attr ref android.R.styleable#RotateDrawable_pivotY 285 */ 286 public float getPivotY() { 287 return mState.mPivotY; 288 } 289 290 /** 291 * Sets whether the Y pivot value represents a fraction of the drawable 292 * height or an absolute value in pixels. 293 * 294 * @param relative True if the Y pivot represents a fraction of the drawable 295 * height, or false if it represents an absolute value in pixels 296 * 297 * @see #isPivotYRelative() 298 */ 299 public void setPivotYRelative(boolean relative) { 300 if (mState.mPivotYRel == relative) { 301 mState.mPivotYRel = relative; 302 invalidateSelf(); 303 } 304 } 305 306 /** 307 * @return True if the Y pivot represents a fraction of the drawable height, 308 * or false if it represents an absolute value in pixels 309 * 310 * @see #setPivotYRelative(boolean) 311 */ 312 public boolean isPivotYRelative() { 313 return mState.mPivotYRel; 314 } 315 316 @Override 317 public void invalidateDrawable(Drawable who) { 318 final Callback callback = getCallback(); 319 if (callback != null) { 320 callback.invalidateDrawable(this); 321 } 322 } 323 324 @Override 325 public void scheduleDrawable(Drawable who, Runnable what, long when) { 326 final Callback callback = getCallback(); 327 if (callback != null) { 328 callback.scheduleDrawable(this, what, when); 329 } 330 } 331 332 @Override 333 public void unscheduleDrawable(Drawable who, Runnable what) { 334 final Callback callback = getCallback(); 335 if (callback != null) { 336 callback.unscheduleDrawable(this, what); 337 } 338 } 339 340 @Override 341 public boolean getPadding(Rect padding) { 342 return mState.mDrawable.getPadding(padding); 343 } 344 345 @Override 346 public boolean setVisible(boolean visible, boolean restart) { 347 mState.mDrawable.setVisible(visible, restart); 348 return super.setVisible(visible, restart); 349 } 350 351 @Override 352 public boolean isStateful() { 353 return mState.mDrawable.isStateful(); 354 } 355 356 @Override 357 protected boolean onStateChange(int[] state) { 358 final boolean changed = mState.mDrawable.setState(state); 359 onBoundsChange(getBounds()); 360 return changed; 361 } 362 363 @Override 364 protected boolean onLevelChange(int level) { 365 mState.mDrawable.setLevel(level); 366 onBoundsChange(getBounds()); 367 368 mState.mCurrentDegrees = mState.mFromDegrees + 369 (mState.mToDegrees - mState.mFromDegrees) * 370 (level / MAX_LEVEL); 371 372 invalidateSelf(); 373 return true; 374 } 375 376 @Override 377 protected void onBoundsChange(Rect bounds) { 378 mState.mDrawable.setBounds(bounds.left, bounds.top, 379 bounds.right, bounds.bottom); 380 } 381 382 @Override 383 public int getIntrinsicWidth() { 384 return mState.mDrawable.getIntrinsicWidth(); 385 } 386 387 @Override 388 public int getIntrinsicHeight() { 389 return mState.mDrawable.getIntrinsicHeight(); 390 } 391 392 @Override 393 public ConstantState getConstantState() { 394 if (mState.canConstantState()) { 395 mState.mChangingConfigurations = getChangingConfigurations(); 396 return mState; 397 } 398 return null; 399 } 400 401 @Override 402 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 403 throws XmlPullParserException, IOException { 404 final TypedArray a = obtainAttributes(r, theme, attrs, 405 com.android.internal.R.styleable.RotateDrawable); 406 407 super.inflateWithAttributes(r, parser, a, 408 com.android.internal.R.styleable.RotateDrawable_visible); 409 410 TypedValue tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotX); 411 final boolean pivotXRel; 412 final float pivotX; 413 if (tv == null) { 414 pivotXRel = true; 415 pivotX = 0.5f; 416 } else { 417 pivotXRel = tv.type == TypedValue.TYPE_FRACTION; 418 pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 419 } 420 421 tv = a.peekValue(com.android.internal.R.styleable.RotateDrawable_pivotY); 422 final boolean pivotYRel; 423 final float pivotY; 424 if (tv == null) { 425 pivotYRel = true; 426 pivotY = 0.5f; 427 } else { 428 pivotYRel = tv.type == TypedValue.TYPE_FRACTION; 429 pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 430 } 431 432 final float fromDegrees = a.getFloat( 433 com.android.internal.R.styleable.RotateDrawable_fromDegrees, 0.0f); 434 final float toDegrees = a.getFloat( 435 com.android.internal.R.styleable.RotateDrawable_toDegrees, 360.0f); 436 437 final int res = a.getResourceId( 438 com.android.internal.R.styleable.RotateDrawable_drawable, 0); 439 Drawable drawable = null; 440 if (res > 0) { 441 drawable = r.getDrawable(res, theme); 442 } 443 444 a.recycle(); 445 446 final int outerDepth = parser.getDepth(); 447 int type; 448 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && 449 (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 450 451 if (type != XmlPullParser.START_TAG) { 452 continue; 453 } 454 455 if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { 456 Log.w("drawable", "Bad element under <rotate>: " 457 + parser .getName()); 458 } 459 } 460 461 if (drawable == null) { 462 Log.w("drawable", "No drawable specified for <rotate>"); 463 } 464 465 final RotateState st = mState; 466 st.mDrawable = drawable; 467 st.mPivotXRel = pivotXRel; 468 st.mPivotX = pivotX; 469 st.mPivotYRel = pivotYRel; 470 st.mPivotY = pivotY; 471 st.mFromDegrees = fromDegrees; 472 st.mCurrentDegrees = fromDegrees; 473 st.mToDegrees = toDegrees; 474 475 if (drawable != null) { 476 drawable.setCallback(this); 477 } 478 } 479 480 @Override 481 public void applyTheme(Theme t) { 482 super.applyTheme(t); 483 484 final RotateState state = mState; 485 if (state == null) { 486 return; 487 } 488 489 if (state.mDrawable != null) { 490 state.mDrawable.applyTheme(t); 491 } 492 } 493 494 @Override 495 public boolean canApplyTheme() { 496 final RotateState state = mState; 497 return state != null && state.mDrawable != null && state.mDrawable.canApplyTheme(); 498 } 499 500 @Override 501 public Drawable mutate() { 502 if (!mMutated && super.mutate() == this) { 503 mState.mDrawable.mutate(); 504 mMutated = true; 505 } 506 return this; 507 } 508 509 /** 510 * @hide 511 */ 512 public void clearMutated() { 513 super.clearMutated(); 514 mState.mDrawable.clearMutated(); 515 mMutated = false; 516 } 517 518 /** 519 * Represents the state of a rotation for a given drawable. The same 520 * rotate drawable can be invoked with different states to drive several 521 * rotations at the same time. 522 */ 523 final static class RotateState extends Drawable.ConstantState { 524 Drawable mDrawable; 525 526 int mChangingConfigurations; 527 528 boolean mPivotXRel; 529 float mPivotX; 530 boolean mPivotYRel; 531 float mPivotY; 532 533 float mFromDegrees; 534 float mToDegrees; 535 536 float mCurrentDegrees; 537 538 private boolean mCanConstantState; 539 private boolean mCheckedConstantState; 540 541 public RotateState(RotateState orig, RotateDrawable owner, Resources res) { 542 if (orig != null) { 543 if (res != null) { 544 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 545 } else { 546 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 547 } 548 mDrawable.setCallback(owner); 549 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 550 mDrawable.setBounds(orig.mDrawable.getBounds()); 551 mDrawable.setLevel(orig.mDrawable.getLevel()); 552 mPivotXRel = orig.mPivotXRel; 553 mPivotX = orig.mPivotX; 554 mPivotYRel = orig.mPivotYRel; 555 mPivotY = orig.mPivotY; 556 mFromDegrees = mCurrentDegrees = orig.mFromDegrees; 557 mToDegrees = orig.mToDegrees; 558 mCanConstantState = mCheckedConstantState = true; 559 } 560 } 561 562 @Override 563 public Drawable newDrawable() { 564 return new RotateDrawable(this, null); 565 } 566 567 @Override 568 public Drawable newDrawable(Resources res) { 569 return new RotateDrawable(this, res); 570 } 571 572 @Override 573 public int getChangingConfigurations() { 574 return mChangingConfigurations; 575 } 576 577 public boolean canConstantState() { 578 if (!mCheckedConstantState) { 579 mCanConstantState = mDrawable.getConstantState() != null; 580 mCheckedConstantState = true; 581 } 582 583 return mCanConstantState; 584 } 585 } 586} 587