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