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 AnimatedRotateDrawable()47 public AnimatedRotateDrawable() { 48 this(null, null); 49 } 50 AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res)51 private AnimatedRotateDrawable(AnimatedRotateState rotateState, Resources res) { 52 mState = new AnimatedRotateState(rotateState, this, res); 53 init(); 54 } 55 init()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 draw(Canvas canvas)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 start()89 public void start() { 90 if (!mRunning) { 91 mRunning = true; 92 nextFrame(); 93 } 94 } 95 stop()96 public void stop() { 97 mRunning = false; 98 unscheduleSelf(this); 99 } 100 isRunning()101 public boolean isRunning() { 102 return mRunning; 103 } 104 nextFrame()105 private void nextFrame() { 106 unscheduleSelf(this); 107 scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration); 108 } 109 run()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 setVisible(boolean visible, boolean restart)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 */ getDrawable()139 public Drawable getDrawable() { 140 return mState.mDrawable; 141 } 142 143 @Override getChangingConfigurations()144 public int getChangingConfigurations() { 145 return super.getChangingConfigurations() 146 | mState.mChangingConfigurations 147 | mState.mDrawable.getChangingConfigurations(); 148 } 149 150 @Override setAlpha(int alpha)151 public void setAlpha(int alpha) { 152 mState.mDrawable.setAlpha(alpha); 153 } 154 155 @Override setColorFilter(ColorFilter cf)156 public void setColorFilter(ColorFilter cf) { 157 mState.mDrawable.setColorFilter(cf); 158 } 159 160 @Override getOpacity()161 public int getOpacity() { 162 return mState.mDrawable.getOpacity(); 163 } 164 invalidateDrawable(Drawable who)165 public void invalidateDrawable(Drawable who) { 166 final Callback callback = getCallback(); 167 if (callback != null) { 168 callback.invalidateDrawable(this); 169 } 170 } 171 scheduleDrawable(Drawable who, Runnable what, long when)172 public void scheduleDrawable(Drawable who, Runnable what, long when) { 173 final Callback callback = getCallback(); 174 if (callback != null) { 175 callback.scheduleDrawable(this, what, when); 176 } 177 } 178 unscheduleDrawable(Drawable who, Runnable what)179 public void unscheduleDrawable(Drawable who, Runnable what) { 180 final Callback callback = getCallback(); 181 if (callback != null) { 182 callback.unscheduleDrawable(this, what); 183 } 184 } 185 186 @Override getPadding(Rect padding)187 public boolean getPadding(Rect padding) { 188 return mState.mDrawable.getPadding(padding); 189 } 190 191 @Override isStateful()192 public boolean isStateful() { 193 return mState.mDrawable.isStateful(); 194 } 195 196 @Override onBoundsChange(Rect bounds)197 protected void onBoundsChange(Rect bounds) { 198 mState.mDrawable.setBounds(bounds.left, bounds.top, bounds.right, bounds.bottom); 199 } 200 201 @Override getIntrinsicWidth()202 public int getIntrinsicWidth() { 203 return mState.mDrawable.getIntrinsicWidth(); 204 } 205 206 @Override getIntrinsicHeight()207 public int getIntrinsicHeight() { 208 return mState.mDrawable.getIntrinsicHeight(); 209 } 210 211 @Override getConstantState()212 public ConstantState getConstantState() { 213 if (mState.canConstantState()) { 214 mState.mChangingConfigurations = getChangingConfigurations(); 215 return mState; 216 } 217 return null; 218 } 219 220 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs)221 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 222 throws XmlPullParserException, IOException { 223 224 final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable); 225 226 super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); 227 228 TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); 229 final boolean pivotXRel = tv.type == TypedValue.TYPE_FRACTION; 230 final float pivotX = pivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 231 232 tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); 233 final boolean pivotYRel = tv.type == TypedValue.TYPE_FRACTION; 234 final float pivotY = pivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 235 236 setFramesCount(a.getInt(R.styleable.AnimatedRotateDrawable_framesCount, 12)); 237 setFramesDuration(a.getInt(R.styleable.AnimatedRotateDrawable_frameDuration, 150)); 238 239 final int res = a.getResourceId(R.styleable.AnimatedRotateDrawable_drawable, 0); 240 Drawable drawable = null; 241 if (res > 0) { 242 drawable = r.getDrawable(res); 243 } 244 245 a.recycle(); 246 247 int outerDepth = parser.getDepth(); 248 int type; 249 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && 250 (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 251 252 if (type != XmlPullParser.START_TAG) { 253 continue; 254 } 255 256 if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { 257 Log.w("drawable", "Bad element under <animated-rotate>: " 258 + parser .getName()); 259 } 260 } 261 262 if (drawable == null) { 263 Log.w("drawable", "No drawable specified for <animated-rotate>"); 264 } 265 266 final AnimatedRotateState rotateState = mState; 267 rotateState.mDrawable = drawable; 268 rotateState.mPivotXRel = pivotXRel; 269 rotateState.mPivotX = pivotX; 270 rotateState.mPivotYRel = pivotYRel; 271 rotateState.mPivotY = pivotY; 272 273 init(); 274 275 if (drawable != null) { 276 drawable.setCallback(this); 277 } 278 } 279 setFramesCount(int framesCount)280 public void setFramesCount(int framesCount) { 281 mState.mFramesCount = framesCount; 282 mIncrement = 360.0f / mState.mFramesCount; 283 } 284 setFramesDuration(int framesDuration)285 public void setFramesDuration(int framesDuration) { 286 mState.mFrameDuration = framesDuration; 287 } 288 289 @Override mutate()290 public Drawable mutate() { 291 if (!mMutated && super.mutate() == this) { 292 mState.mDrawable.mutate(); 293 mMutated = true; 294 } 295 return this; 296 } 297 298 final static class AnimatedRotateState extends Drawable.ConstantState { 299 Drawable mDrawable; 300 301 int mChangingConfigurations; 302 303 boolean mPivotXRel; 304 float mPivotX; 305 boolean mPivotYRel; 306 float mPivotY; 307 int mFrameDuration; 308 int mFramesCount; 309 310 private boolean mCanConstantState; 311 private boolean mCheckedConstantState; 312 AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner, Resources res)313 public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner, 314 Resources res) { 315 if (source != null) { 316 if (res != null) { 317 mDrawable = source.mDrawable.getConstantState().newDrawable(res); 318 } else { 319 mDrawable = source.mDrawable.getConstantState().newDrawable(); 320 } 321 mDrawable.setCallback(owner); 322 mPivotXRel = source.mPivotXRel; 323 mPivotX = source.mPivotX; 324 mPivotYRel = source.mPivotYRel; 325 mPivotY = source.mPivotY; 326 mFramesCount = source.mFramesCount; 327 mFrameDuration = source.mFrameDuration; 328 mCanConstantState = mCheckedConstantState = true; 329 } 330 } 331 332 @Override newDrawable()333 public Drawable newDrawable() { 334 return new AnimatedRotateDrawable(this, null); 335 } 336 337 @Override newDrawable(Resources res)338 public Drawable newDrawable(Resources res) { 339 return new AnimatedRotateDrawable(this, res); 340 } 341 342 @Override getChangingConfigurations()343 public int getChangingConfigurations() { 344 return mChangingConfigurations; 345 } 346 canConstantState()347 public boolean canConstantState() { 348 if (!mCheckedConstantState) { 349 mCanConstantState = mDrawable.getConstantState() != null; 350 mCheckedConstantState = true; 351 } 352 353 return mCanConstantState; 354 } 355 } 356 } 357