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