AnimatedRotateDrawable.java revision a12962207155305da44b5a1b8fb9acaed358c14c
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.annotation.NonNull; 20import android.annotation.Nullable; 21import android.graphics.Canvas; 22import android.graphics.Rect; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.content.res.Resources.Theme; 26import android.util.AttributeSet; 27import android.util.TypedValue; 28import android.os.SystemClock; 29 30import org.xmlpull.v1.XmlPullParser; 31import org.xmlpull.v1.XmlPullParserException; 32 33import java.io.IOException; 34 35import com.android.internal.R; 36 37/** 38 * @hide 39 */ 40public class AnimatedRotateDrawable extends DrawableWrapper implements Runnable, Animatable { 41 private AnimatedRotateState mState; 42 43 private float mCurrentDegrees; 44 private float mIncrement; 45 private boolean mRunning; 46 47 public AnimatedRotateDrawable() { 48 this(new AnimatedRotateState(null), null); 49 } 50 51 @Override 52 public void draw(Canvas canvas) { 53 int saveCount = canvas.save(); 54 55 final AnimatedRotateState st = mState; 56 final Drawable drawable = st.mDrawable; 57 final Rect bounds = drawable.getBounds(); 58 59 int w = bounds.right - bounds.left; 60 int h = bounds.bottom - bounds.top; 61 62 float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX; 63 float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY; 64 65 canvas.rotate(mCurrentDegrees, px + bounds.left, py + bounds.top); 66 67 drawable.draw(canvas); 68 69 canvas.restoreToCount(saveCount); 70 } 71 72 @Override 73 public void start() { 74 if (!mRunning) { 75 mRunning = true; 76 nextFrame(); 77 } 78 } 79 80 @Override 81 public void stop() { 82 mRunning = false; 83 unscheduleSelf(this); 84 } 85 86 @Override 87 public boolean isRunning() { 88 return mRunning; 89 } 90 91 private void nextFrame() { 92 unscheduleSelf(this); 93 scheduleSelf(this, SystemClock.uptimeMillis() + mState.mFrameDuration); 94 } 95 96 @Override 97 public void run() { 98 // TODO: This should be computed in draw(Canvas), based on the amount 99 // of time since the last frame drawn 100 mCurrentDegrees += mIncrement; 101 if (mCurrentDegrees > (360.0f - mIncrement)) { 102 mCurrentDegrees = 0.0f; 103 } 104 invalidateSelf(); 105 nextFrame(); 106 } 107 108 @Override 109 public boolean setVisible(boolean visible, boolean restart) { 110 final boolean changed = super.setVisible(visible, restart); 111 if (visible) { 112 if (changed || restart) { 113 mCurrentDegrees = 0.0f; 114 nextFrame(); 115 } 116 } else { 117 unscheduleSelf(this); 118 } 119 return changed; 120 } 121 122 @Override 123 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 124 @NonNull AttributeSet attrs, @Nullable Theme theme) 125 throws XmlPullParserException, IOException { 126 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.AnimatedRotateDrawable); 127 super.inflateWithAttributes(r, parser, a, R.styleable.AnimatedRotateDrawable_visible); 128 updateStateFromTypedArray(a); 129 verifyRequiredAttributes(a); 130 a.recycle(); 131 132 updateLocalState(); 133 } 134 135 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 136 // If we're not waiting on a theme, verify required attributes. 137 if (getDrawable() == null && (mState.mThemeAttrs == null 138 || mState.mThemeAttrs[R.styleable.AnimatedRotateDrawable_drawable] == 0)) { 139 throw new XmlPullParserException(a.getPositionDescription() 140 + ": <animated-rotate> tag requires a 'drawable' attribute or " 141 + "child tag defining a drawable"); 142 } 143 } 144 145 @Override 146 void updateStateFromTypedArray(TypedArray a) { 147 super.updateStateFromTypedArray(a); 148 149 final AnimatedRotateState state = mState; 150 151 if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotX)) { 152 final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotX); 153 state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION; 154 state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 155 } 156 157 if (a.hasValue(R.styleable.AnimatedRotateDrawable_pivotY)) { 158 final TypedValue tv = a.peekValue(R.styleable.AnimatedRotateDrawable_pivotY); 159 state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION; 160 state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 161 } 162 163 setFramesCount(a.getInt( 164 R.styleable.AnimatedRotateDrawable_framesCount, state.mFramesCount)); 165 setFramesDuration(a.getInt( 166 R.styleable.AnimatedRotateDrawable_frameDuration, state.mFrameDuration)); 167 168 final Drawable dr = a.getDrawable(R.styleable.AnimatedRotateDrawable_drawable); 169 if (dr != null) { 170 setDrawable(dr); 171 } 172 } 173 174 @Override 175 public void applyTheme(@Nullable Theme t) { 176 final AnimatedRotateState state = mState; 177 if (state == null) { 178 return; 179 } 180 181 if (state.mThemeAttrs != null) { 182 final TypedArray a = t.resolveAttributes( 183 state.mThemeAttrs, R.styleable.AnimatedRotateDrawable); 184 try { 185 updateStateFromTypedArray(a); 186 verifyRequiredAttributes(a); 187 } catch (XmlPullParserException e) { 188 throw new RuntimeException(e); 189 } finally { 190 a.recycle(); 191 } 192 } 193 194 // The drawable may have changed as a result of applying the theme, so 195 // apply the theme to the wrapped drawable last. 196 super.applyTheme(t); 197 198 updateLocalState(); 199 } 200 201 public void setFramesCount(int framesCount) { 202 mState.mFramesCount = framesCount; 203 mIncrement = 360.0f / mState.mFramesCount; 204 } 205 206 public void setFramesDuration(int framesDuration) { 207 mState.mFrameDuration = framesDuration; 208 } 209 210 static final class AnimatedRotateState extends DrawableWrapper.DrawableWrapperState { 211 Drawable mDrawable; 212 int[] mThemeAttrs; 213 214 int mChangingConfigurations; 215 216 boolean mPivotXRel = false; 217 float mPivotX = 0; 218 boolean mPivotYRel = false; 219 float mPivotY = 0; 220 int mFrameDuration = 150; 221 int mFramesCount = 12; 222 223 private boolean mCanConstantState; 224 private boolean mCheckedConstantState; 225 226 public AnimatedRotateState(AnimatedRotateState orig) { 227 super(orig); 228 229 if (orig != null) { 230 mPivotXRel = orig.mPivotXRel; 231 mPivotX = orig.mPivotX; 232 mPivotYRel = orig.mPivotYRel; 233 mPivotY = orig.mPivotY; 234 mFramesCount = orig.mFramesCount; 235 mFrameDuration = orig.mFrameDuration; 236 } 237 } 238 239 @Override 240 public Drawable newDrawable(Resources res) { 241 return new AnimatedRotateDrawable(this, res); 242 } 243 244 @Override 245 public boolean canApplyTheme() { 246 return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme()) 247 || super.canApplyTheme(); 248 } 249 250 @Override 251 public int getChangingConfigurations() { 252 return mChangingConfigurations; 253 } 254 255 public boolean canConstantState() { 256 if (!mCheckedConstantState) { 257 mCanConstantState = mDrawable.getConstantState() != null; 258 mCheckedConstantState = true; 259 } 260 261 return mCanConstantState; 262 } 263 } 264 265 private AnimatedRotateDrawable(AnimatedRotateState state, Resources res) { 266 super(state, res); 267 268 mState = state; 269 270 updateLocalState(); 271 } 272 273 private void updateLocalState() { 274 final AnimatedRotateState state = mState; 275 mIncrement = 360.0f / state.mFramesCount; 276 277 // Force the wrapped drawable to use filtering and AA, if applicable, 278 // so that it looks smooth when rotated. 279 final Drawable drawable = state.mDrawable; 280 if (drawable != null) { 281 drawable.setFilterBitmap(true); 282 if (drawable instanceof BitmapDrawable) { 283 ((BitmapDrawable) drawable).setAntiAlias(true); 284 } 285 } 286 } 287} 288