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