AnimatedRotateDrawable.java revision a426445dfdab43886dd894f2ba8a1d55bfcbb278
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 = r.obtainAttributes(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);
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    public void setFramesCount(int framesCount) {
317        mState.mFramesCount = framesCount;
318        mIncrement = 360.0f / mState.mFramesCount;
319    }
320
321    public void setFramesDuration(int framesDuration) {
322        mState.mFrameDuration = framesDuration;
323    }
324
325    @Override
326    public Drawable mutate() {
327        if (!mMutated && super.mutate() == this) {
328            mState.mDrawable.mutate();
329            mMutated = true;
330        }
331        return this;
332    }
333
334    final static class AnimatedRotateState extends Drawable.ConstantState {
335        Drawable mDrawable;
336
337        int mChangingConfigurations;
338
339        boolean mPivotXRel;
340        float mPivotX;
341        boolean mPivotYRel;
342        float mPivotY;
343        int mFrameDuration;
344        int mFramesCount;
345
346        private boolean mCanConstantState;
347        private boolean mCheckedConstantState;
348
349        public AnimatedRotateState(AnimatedRotateState source, AnimatedRotateDrawable owner,
350                Resources res) {
351            if (source != null) {
352                if (res != null) {
353                    mDrawable = source.mDrawable.getConstantState().newDrawable(res);
354                } else {
355                    mDrawable = source.mDrawable.getConstantState().newDrawable();
356                }
357                mDrawable.setCallback(owner);
358                mDrawable.setLayoutDirection(source.mDrawable.getLayoutDirection());
359                mPivotXRel = source.mPivotXRel;
360                mPivotX = source.mPivotX;
361                mPivotYRel = source.mPivotYRel;
362                mPivotY = source.mPivotY;
363                mFramesCount = source.mFramesCount;
364                mFrameDuration = source.mFrameDuration;
365                mCanConstantState = mCheckedConstantState = true;
366            }
367        }
368
369        @Override
370        public Drawable newDrawable() {
371            return new AnimatedRotateDrawable(this, null);
372        }
373
374        @Override
375        public Drawable newDrawable(Resources res) {
376            return new AnimatedRotateDrawable(this, res);
377        }
378
379        @Override
380        public int getChangingConfigurations() {
381            return mChangingConfigurations;
382        }
383
384        public boolean canConstantState() {
385            if (!mCheckedConstantState) {
386                mCanConstantState = mDrawable.getConstantState() != null;
387                mCheckedConstantState = true;
388            }
389
390            return mCanConstantState;
391        }
392    }
393}
394