AnimatedRotateDrawable.java revision 17cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8b
1/* 2 * Copyright (C) 2009 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.Canvas; 20import android.graphics.Rect; 21import android.graphics.ColorFilter; 22import android.graphics.PorterDuff.Mode; 23import android.content.res.ColorStateList; 24import android.content.res.Resources; 25import android.content.res.TypedArray; 26import android.content.res.Resources.Theme; 27import android.util.AttributeSet; 28import android.util.TypedValue; 29import android.util.Log; 30import android.os.SystemClock; 31 32import org.xmlpull.v1.XmlPullParser; 33import org.xmlpull.v1.XmlPullParserException; 34 35import java.io.IOException; 36 37import com.android.internal.R; 38 39/** 40 * @hide 41 */ 42public class AnimatedRotateDrawable extends Drawable implements Drawable.Callback, Runnable, 43 Animatable { 44 45 private AnimatedRotateState mState; 46 private boolean mMutated; 47 private float mCurrentDegrees; 48 private float mIncrement; 49 private boolean mRunning; 50 51 public AnimatedRotateDrawable() { 52 this(null, null); 53 } 54 55 private AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res) { 56 mState = new AnimatedRotateState(rotateState, this, res); 57 init(); 58 } 59 60 private void init() { 61 final AnimatedRotateState state = mState; 62 mIncrement = 360.0f / state.mFramesCount; 63 final Drawable drawable = state.mDrawable; 64 if (drawable != null) { 65 drawable.setFilterBitmap(true); 66 if (drawable instanceof BitmapDrawable) { 67 ((BitmapDrawable) drawable).setAntiAlias(true); 68 } 69 } 70 } 71 72 @Override 73 public void draw(Canvas canvas) { 74 int saveCount = canvas.save(); 75 76 final AnimatedRotateState st = mState; 77 final Drawable drawable = st.mDrawable; 78 final Rect bounds = drawable.getBounds(); 79 80 int w = bounds.right - bounds.left; 81 int h = bounds.bottom - bounds.top; 82 83 float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; 84 float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; 85 86 canvas.rotate(mCurrentDegrees, px + bounds.left, py + bounds.top); 87 88 drawable.draw(canvas); 89 90 canvas.restoreToCount(saveCount); 91 } 92 93 @Override 94 public void start() { 95 if (!mRunning) { 96 mRunning = true; 97 nextFrame(); 98 } 99 } 100 101 @Override 102 public void stop() { 103 mRunning = false; 104 unscheduleSelf(this); 105 } 106 107 @Override 108 public boolean isRunning() { 109 return mRunning; 110 } 111 112 private void nextFrame() { 113 unscheduleSelf(this); 114 scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration); 115 } 116 117 @Override 118 public void run() { 119 // TODO: This should be computed in draw(Canvas), based on the amount 120 // of time since the last frame drawn 121 mCurrentDegrees += mIncrement; 122 if (mCurrentDegrees > (360.0f - mIncrement)) { 123 mCurrentDegrees = 0.0f; 124 } 125 invalidateSelf(); 126 nextFrame(); 127 } 128 129 @Override 130 public boolean setVisible(boolean visible, boolean restart) { 131 mState.mDrawable.setVisible(visible, restart); 132 boolean changed = super.setVisible(visible, restart); 133 if (visible) { 134 if (changed || restart) { 135 mCurrentDegrees = 0.0f; 136 nextFrame(); 137 } 138 } else { 139 unscheduleSelf(this); 140 } 141 return changed; 142 } 143 144 /** 145 * Returns the drawable rotated by this RotateDrawable. 146 */ 147 public Drawable getDrawable() { 148 return mState.mDrawable; 149 } 150 151 @Override 152 public int getChangingConfigurations() { 153 return super.getChangingConfigurations() 154 | mState.mChangingConfigurations 155 | mState.mDrawable.getChangingConfigurations(); 156 } 157 158 @Override 159 public void setAlpha(int alpha) { 160 mState.mDrawable.setAlpha(alpha); 161 } 162 163 @Override 164 public int getAlpha() { 165 return mState.mDrawable.getAlpha(); 166 } 167 168 @Override 169 public void setColorFilter(ColorFilter cf) { 170 mState.mDrawable.setColorFilter(cf); 171 } 172 173 @Override 174 public void setTintList(ColorStateList tint) { 175 mState.mDrawable.setTintList(tint); 176 } 177 178 @Override 179 public void setTintMode(Mode tintMode) { 180 mState.mDrawable.setTintMode(tintMode); 181 } 182 183 @Override 184 public int getOpacity() { 185 return mState.mDrawable.getOpacity(); 186 } 187 188 @Override 189 public void invalidateDrawable(Drawable who) { 190 final Callback callback = getCallback(); 191 if (callback != null) { 192 callback.invalidateDrawable(this); 193 } 194 } 195 196 @Override 197 public void scheduleDrawable(Drawable who, Runnable what, long when) { 198 final Callback callback = getCallback(); 199 if (callback != null) { 200 callback.scheduleDrawable(this, what, when); 201 } 202 } 203 204 @Override 205 public void unscheduleDrawable(Drawable who, Runnable what) { 206 final Callback callback = getCallback(); 207 if (callback != null) { 208 callback.unscheduleDrawable(this, what); 209 } 210 } 211 212 @Override 213 public boolean getPadding(Rect padding) { 214 return mState.mDrawable.getPadding(padding); 215 } 216 217 @Override 218 public boolean isStateful() { 219 return mState.mDrawable.isStateful(); 220 } 221 222 @Override 223 protected void onBoundsChange(Rect bounds) { 224 mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom); 225 } 226 227 @Override 228 protected boolean onLevelChange(int level) { 229 return mState.mDrawable.setLevel(level); 230 } 231 232 @Override 233 protected boolean onStateChange(int[] state) { 234 return mState.mDrawable.setState(state); 235 } 236 237 @Override 238 public int getIntrinsicWidth() { 239 return mState.mDrawable.getIntrinsicWidth(); 240 } 241 242 @Override 243 public int getIntrinsicHeight() { 244 return mState.mDrawable.getIntrinsicHeight(); 245 } 246 247 @Override 248 public ConstantState getConstantState() { 249 if (mState.canConstantState()) { 250 mState.mChangingConfigurations = getChangingConfigurations(); 251 return mState; 252 } 253 return null; 254 } 255 256 @Override 257 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 258 throws XmlPullParserException, IOException { 259 260 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); 261 262 super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); 263 264 TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); 265 final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION; 266 final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 267 268 tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); 269 final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION; 270 final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 271 272 setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12)); 273 setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150)); 274 275 final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0); 276 Drawable drawable = null; 277 if (res > 0) { 278 drawable = r.getDrawable(res, theme); 279 } 280 281 a.recycle(); 282 283 int outerDepth = parser.getDepth(); 284 int type; 285 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && 286 (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 287 288 if (type != XmlPullParser.START_TAG) { 289 continue; 290 } 291 292 if ((drawable = Drawable.createFromXmlInner(r, parser, attrs, theme)) == null) { 293 Log.w("drawable", "Bad element under <animated-rotate>: " 294 + parser .getName()); 295 } 296 } 297 298 if (drawable == null) { 299 Log.w("drawable", "No drawable specified for <animated-rotate>"); 300 } 301 302 final AnimatedRotateState rotateState = mState; 303 rotateState.mDrawable = drawable; 304 rotateState.mPivotXRel = pivotXRel; 305 rotateState.mPivotX = pivotX; 306 rotateState.mPivotYRel = pivotYRel; 307 rotateState.mPivotY = pivotY; 308 309 init(); 310 311 if (drawable != null) { 312 drawable.setCallback(this); 313 } 314 } 315 316 @Override 317 public void applyTheme(Theme t) { 318 super.applyTheme(t); 319 320 final AnimatedRotateState state = mState; 321 if (state == null) { 322 return; 323 } 324 325 if (state.mDrawable != null) { 326 state.mDrawable.applyTheme(t); 327 } 328 } 329 330 @Override 331 public boolean canApplyTheme() { 332 final AnimatedRotateState state = mState; 333 return state != null && state.mDrawable != null && state.mDrawable.canApplyTheme(); 334 } 335 336 public void setFramesCount(int framesCount) { 337 mState.mFramesCount = framesCount; 338 mIncrement = 360.0f / mState.mFramesCount; 339 } 340 341 public void setFramesDuration(int framesDuration) { 342 mState.mFrameDuration = framesDuration; 343 } 344 345 @Override 346 public Drawable mutate() { 347 if (!mMutated && super.mutate() == this) { 348 mState.mDrawable.mutate(); 349 mMutated = true; 350 } 351 return this; 352 } 353 354 /** 355 * @hide 356 */ 357 public void clearMutated() { 358 super.clearMutated(); 359 mState.mDrawable.clearMutated(); 360 mMutated = false; 361 } 362 363 final static class AnimatedRotateState extends Drawable.ConstantState { 364 Drawable mDrawable; 365 366 int mChangingConfigurations; 367 368 boolean mPivotXRel; 369 float mPivotX; 370 boolean mPivotYRel; 371 float mPivotY; 372 int mFrameDuration; 373 int mFramesCount; 374 375 private boolean mCanConstantState; 376 private boolean mCheckedConstantState; 377 378 public AnimatedRotateState(AnimatedRotateState orig, AnimatedRotateDrawable owner, 379 Resources res) { 380 if (orig != null) { 381 if (res != null) { 382 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 383 } else { 384 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 385 } 386 mDrawable.setCallback(owner); 387 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 388 mDrawable.setBounds(orig.mDrawable.getBounds()); 389 mDrawable.setLevel(orig.mDrawable.getLevel()); 390 mPivotXRel = orig.mPivotXRel; 391 mPivotX = orig.mPivotX; 392 mPivotYRel = orig.mPivotYRel; 393 mPivotY = orig.mPivotY; 394 mFramesCount = orig.mFramesCount; 395 mFrameDuration = orig.mFrameDuration; 396 mCanConstantState = mCheckedConstantState = true; 397 } 398 } 399 400 @Override 401 public Drawable newDrawable() { 402 return new AnimatedRotateDrawable(this, null); 403 } 404 405 @Override 406 public Drawable newDrawable(Resources res) { 407 return new AnimatedRotateDrawable(this, res); 408 } 409 410 @Override 411 public int getChangingConfigurations() { 412 return mChangingConfigurations; 413 } 414 415 public boolean canConstantState() { 416 if (!mCheckedConstantState) { 417 mCanConstantState = mDrawable.getConstantState() != null; 418 mCheckedConstantState = true; 419 } 420 421 return mCanConstantState; 422 } 423 } 424} 425