ProgressBar.java revision 6af97e1c20df4d7010fafd7059c95d9b4113e4a6
1/*
2 * Copyright (C) 2006 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.widget;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.graphics.Bitmap;
22import android.graphics.BitmapShader;
23import android.graphics.Canvas;
24import android.graphics.Rect;
25import android.graphics.Shader;
26import android.graphics.drawable.Animatable;
27import android.graphics.drawable.AnimationDrawable;
28import android.graphics.drawable.BitmapDrawable;
29import android.graphics.drawable.ClipDrawable;
30import android.graphics.drawable.Drawable;
31import android.graphics.drawable.LayerDrawable;
32import android.graphics.drawable.ShapeDrawable;
33import android.graphics.drawable.StateListDrawable;
34import android.graphics.drawable.shapes.RoundRectShape;
35import android.graphics.drawable.shapes.Shape;
36import android.os.Parcel;
37import android.os.Parcelable;
38import android.os.SystemClock;
39import android.util.AttributeSet;
40import android.view.Gravity;
41import android.view.RemotableViewMethod;
42import android.view.View;
43import android.view.ViewDebug;
44import android.view.animation.AlphaAnimation;
45import android.view.animation.Animation;
46import android.view.animation.AnimationUtils;
47import android.view.animation.Interpolator;
48import android.view.animation.LinearInterpolator;
49import android.view.animation.Transformation;
50import android.widget.RemoteViews.RemoteView;
51
52import com.android.internal.R;
53
54
55/**
56 * <p>
57 * Visual indicator of progress in some operation.  Displays a bar to the user
58 * representing how far the operation has progressed; the application can
59 * change the amount of progress (modifying the length of the bar) as it moves
60 * forward.  There is also a secondary progress displayable on a progress bar
61 * which is useful for displaying intermediate progress, such as the buffer
62 * level during a streaming playback progress bar.
63 * </p>
64 *
65 * <p>
66 * A progress bar can also be made indeterminate. In indeterminate mode, the
67 * progress bar shows a cyclic animation. This mode is used by applications
68 * when the length of the task is unknown.
69 * </p>
70 *
71 * <p>The following code example shows how a progress bar can be used from
72 * a worker thread to update the user interface to notify the user of progress:
73 * </p>
74 *
75 * <pre class="prettyprint">
76 * public class MyActivity extends Activity {
77 *     private static final int PROGRESS = 0x1;
78 *
79 *     private ProgressBar mProgress;
80 *     private int mProgressStatus = 0;
81 *
82 *     private Handler mHandler = new Handler();
83 *
84 *     protected void onCreate(Bundle icicle) {
85 *         super.onCreate(icicle);
86 *
87 *         setContentView(R.layout.progressbar_activity);
88 *
89 *         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
90 *
91 *         // Start lengthy operation in a background thread
92 *         new Thread(new Runnable() {
93 *             public void run() {
94 *                 while (mProgressStatus < 100) {
95 *                     mProgressStatus = doWork();
96 *
97 *                     // Update the progress bar
98 *                     mHandler.post(new Runnable() {
99 *                         public void run() {
100 *                             mProgress.setProgress(mProgressStatus);
101 *                         }
102 *                     });
103 *                 }
104 *             }
105 *         }).start();
106 *     }
107 * }
108 * </pre>
109 *
110 * <p><strong>XML attributes</b></strong>
111 * <p>
112 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes},
113 * {@link android.R.styleable#View View Attributes}
114 * </p>
115 *
116 * <p><strong>Styles</b></strong>
117 * <p>
118 * @attr ref android.R.styleable#Theme_progressBarStyle
119 * @attr ref android.R.styleable#Theme_progressBarStyleSmall
120 * @attr ref android.R.styleable#Theme_progressBarStyleLarge
121 * @attr ref android.R.styleable#Theme_progressBarStyleHorizontal
122 * </p>
123 */
124@RemoteView
125public class ProgressBar extends View {
126    private static final int MAX_LEVEL = 10000;
127    private static final int ANIMATION_RESOLUTION = 200;
128
129    int mMinWidth;
130    int mMaxWidth;
131    int mMinHeight;
132    int mMaxHeight;
133
134    private int mProgress;
135    private int mSecondaryProgress;
136    private int mMax;
137
138    private int mBehavior;
139    private int mDuration;
140    private boolean mIndeterminate;
141    private boolean mOnlyIndeterminate;
142    private Transformation mTransformation;
143    private AlphaAnimation mAnimation;
144    private Drawable mIndeterminateDrawable;
145    private Drawable mProgressDrawable;
146    private Drawable mCurrentDrawable;
147    Bitmap mSampleTile;
148    private boolean mNoInvalidate;
149    private Interpolator mInterpolator;
150    private RefreshProgressRunnable mRefreshProgressRunnable;
151    private long mUiThreadId;
152    private boolean mShouldStartAnimationDrawable;
153    private long mLastDrawTime;
154
155    private boolean mInDrawing;
156
157    private int mAnimationResolution;
158
159    /**
160     * Create a new progress bar with range 0...100 and initial progress of 0.
161     * @param context the application environment
162     */
163    public ProgressBar(Context context) {
164        this(context, null);
165    }
166
167    public ProgressBar(Context context, AttributeSet attrs) {
168        this(context, attrs, com.android.internal.R.attr.progressBarStyle);
169    }
170
171    public ProgressBar(Context context, AttributeSet attrs, int defStyle) {
172        this(context, attrs, defStyle, 0);
173    }
174
175    /**
176     * @hide
177     */
178    public ProgressBar(Context context, AttributeSet attrs, int defStyle, int styleRes) {
179        super(context, attrs, defStyle);
180        mUiThreadId = Thread.currentThread().getId();
181        initProgressBar();
182
183        TypedArray a =
184            context.obtainStyledAttributes(attrs, R.styleable.ProgressBar, defStyle, styleRes);
185
186        mNoInvalidate = true;
187
188        Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable);
189        if (drawable != null) {
190            drawable = tileify(drawable, false);
191            // Calling this method can set mMaxHeight, make sure the corresponding
192            // XML attribute for mMaxHeight is read after calling this method
193            setProgressDrawable(drawable);
194        }
195
196
197        mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration);
198
199        mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth);
200        mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth);
201        mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight);
202        mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight);
203
204        mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior);
205
206        final int resID = a.getResourceId(
207                com.android.internal.R.styleable.ProgressBar_interpolator,
208                android.R.anim.linear_interpolator); // default to linear interpolator
209        if (resID > 0) {
210            setInterpolator(context, resID);
211        }
212
213        setMax(a.getInt(R.styleable.ProgressBar_max, mMax));
214
215        setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress));
216
217        setSecondaryProgress(
218                a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress));
219
220        drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable);
221        if (drawable != null) {
222            drawable = tileifyIndeterminate(drawable);
223            setIndeterminateDrawable(drawable);
224        }
225
226        mOnlyIndeterminate = a.getBoolean(
227                R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate);
228
229        mNoInvalidate = false;
230
231        setIndeterminate(mOnlyIndeterminate || a.getBoolean(
232                R.styleable.ProgressBar_indeterminate, mIndeterminate));
233
234        mAnimationResolution = a.getInteger(R.styleable.ProgressBar_animationResolution,
235                ANIMATION_RESOLUTION);
236
237        a.recycle();
238    }
239
240    /**
241     * Converts a drawable to a tiled version of itself. It will recursively
242     * traverse layer and state list drawables.
243     */
244    private Drawable tileify(Drawable drawable, boolean clip) {
245
246        if (drawable instanceof LayerDrawable) {
247            LayerDrawable background = (LayerDrawable) drawable;
248            final int N = background.getNumberOfLayers();
249            Drawable[] outDrawables = new Drawable[N];
250
251            for (int i = 0; i < N; i++) {
252                int id = background.getId(i);
253                outDrawables[i] = tileify(background.getDrawable(i),
254                        (id == R.id.progress || id == R.id.secondaryProgress));
255            }
256
257            LayerDrawable newBg = new LayerDrawable(outDrawables);
258
259            for (int i = 0; i < N; i++) {
260                newBg.setId(i, background.getId(i));
261            }
262
263            return newBg;
264
265        } else if (drawable instanceof StateListDrawable) {
266            StateListDrawable in = (StateListDrawable) drawable;
267            StateListDrawable out = new StateListDrawable();
268            int numStates = in.getStateCount();
269            for (int i = 0; i < numStates; i++) {
270                out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip));
271            }
272            return out;
273
274        } else if (drawable instanceof BitmapDrawable) {
275            final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
276            if (mSampleTile == null) {
277                mSampleTile = tileBitmap;
278            }
279
280            final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape());
281
282            final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
283                    Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
284            shapeDrawable.getPaint().setShader(bitmapShader);
285
286            return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
287                    ClipDrawable.HORIZONTAL) : shapeDrawable;
288        }
289
290        return drawable;
291    }
292
293    Shape getDrawableShape() {
294        final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 };
295        return new RoundRectShape(roundedCorners, null, null);
296    }
297
298    /**
299     * Convert a AnimationDrawable for use as a barberpole animation.
300     * Each frame of the animation is wrapped in a ClipDrawable and
301     * given a tiling BitmapShader.
302     */
303    private Drawable tileifyIndeterminate(Drawable drawable) {
304        if (drawable instanceof AnimationDrawable) {
305            AnimationDrawable background = (AnimationDrawable) drawable;
306            final int N = background.getNumberOfFrames();
307            AnimationDrawable newBg = new AnimationDrawable();
308            newBg.setOneShot(background.isOneShot());
309
310            for (int i = 0; i < N; i++) {
311                Drawable frame = tileify(background.getFrame(i), true);
312                frame.setLevel(10000);
313                newBg.addFrame(frame, background.getDuration(i));
314            }
315            newBg.setLevel(10000);
316            drawable = newBg;
317        }
318        return drawable;
319    }
320
321    /**
322     * <p>
323     * Initialize the progress bar's default values:
324     * </p>
325     * <ul>
326     * <li>progress = 0</li>
327     * <li>max = 100</li>
328     * <li>animation duration = 4000 ms</li>
329     * <li>indeterminate = false</li>
330     * <li>behavior = repeat</li>
331     * </ul>
332     */
333    private void initProgressBar() {
334        mMax = 100;
335        mProgress = 0;
336        mSecondaryProgress = 0;
337        mIndeterminate = false;
338        mOnlyIndeterminate = false;
339        mDuration = 4000;
340        mBehavior = AlphaAnimation.RESTART;
341        mMinWidth = 24;
342        mMaxWidth = 48;
343        mMinHeight = 24;
344        mMaxHeight = 48;
345    }
346
347    /**
348     * <p>Indicate whether this progress bar is in indeterminate mode.</p>
349     *
350     * @return true if the progress bar is in indeterminate mode
351     */
352    @ViewDebug.ExportedProperty(category = "progress")
353    public synchronized boolean isIndeterminate() {
354        return mIndeterminate;
355    }
356
357    /**
358     * <p>Change the indeterminate mode for this progress bar. In indeterminate
359     * mode, the progress is ignored and the progress bar shows an infinite
360     * animation instead.</p>
361     *
362     * If this progress bar's style only supports indeterminate mode (such as the circular
363     * progress bars), then this will be ignored.
364     *
365     * @param indeterminate true to enable the indeterminate mode
366     */
367    @android.view.RemotableViewMethod
368    public synchronized void setIndeterminate(boolean indeterminate) {
369        if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) {
370            mIndeterminate = indeterminate;
371
372            if (indeterminate) {
373                // swap between indeterminate and regular backgrounds
374                mCurrentDrawable = mIndeterminateDrawable;
375                startAnimation();
376            } else {
377                mCurrentDrawable = mProgressDrawable;
378                stopAnimation();
379            }
380        }
381    }
382
383    /**
384     * <p>Get the drawable used to draw the progress bar in
385     * indeterminate mode.</p>
386     *
387     * @return a {@link android.graphics.drawable.Drawable} instance
388     *
389     * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable)
390     * @see #setIndeterminate(boolean)
391     */
392    public Drawable getIndeterminateDrawable() {
393        return mIndeterminateDrawable;
394    }
395
396    /**
397     * <p>Define the drawable used to draw the progress bar in
398     * indeterminate mode.</p>
399     *
400     * @param d the new drawable
401     *
402     * @see #getIndeterminateDrawable()
403     * @see #setIndeterminate(boolean)
404     */
405    public void setIndeterminateDrawable(Drawable d) {
406        if (d != null) {
407            d.setCallback(this);
408        }
409        mIndeterminateDrawable = d;
410        if (mIndeterminate) {
411            mCurrentDrawable = d;
412            postInvalidate();
413        }
414    }
415
416    /**
417     * <p>Get the drawable used to draw the progress bar in
418     * progress mode.</p>
419     *
420     * @return a {@link android.graphics.drawable.Drawable} instance
421     *
422     * @see #setProgressDrawable(android.graphics.drawable.Drawable)
423     * @see #setIndeterminate(boolean)
424     */
425    public Drawable getProgressDrawable() {
426        return mProgressDrawable;
427    }
428
429    /**
430     * <p>Define the drawable used to draw the progress bar in
431     * progress mode.</p>
432     *
433     * @param d the new drawable
434     *
435     * @see #getProgressDrawable()
436     * @see #setIndeterminate(boolean)
437     */
438    public void setProgressDrawable(Drawable d) {
439        if (d != null) {
440            d.setCallback(this);
441
442            // Make sure the ProgressBar is always tall enough
443            int drawableHeight = d.getMinimumHeight();
444            if (mMaxHeight < drawableHeight) {
445                mMaxHeight = drawableHeight;
446                requestLayout();
447            }
448        }
449        mProgressDrawable = d;
450        if (!mIndeterminate) {
451            mCurrentDrawable = d;
452            postInvalidate();
453        }
454    }
455
456    /**
457     * @return The drawable currently used to draw the progress bar
458     */
459    Drawable getCurrentDrawable() {
460        return mCurrentDrawable;
461    }
462
463    @Override
464    protected boolean verifyDrawable(Drawable who) {
465        return who == mProgressDrawable || who == mIndeterminateDrawable
466                || super.verifyDrawable(who);
467    }
468
469    @Override
470    public void jumpDrawablesToCurrentState() {
471        super.jumpDrawablesToCurrentState();
472        if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState();
473        if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState();
474    }
475
476    @Override
477    public void postInvalidate() {
478        if (!mNoInvalidate) {
479            super.postInvalidate();
480        }
481    }
482
483    private class RefreshProgressRunnable implements Runnable {
484
485        private int mId;
486        private int mProgress;
487        private boolean mFromUser;
488
489        RefreshProgressRunnable(int id, int progress, boolean fromUser) {
490            mId = id;
491            mProgress = progress;
492            mFromUser = fromUser;
493        }
494
495        public void run() {
496            doRefreshProgress(mId, mProgress, mFromUser);
497            // Put ourselves back in the cache when we are done
498            mRefreshProgressRunnable = this;
499        }
500
501        public void setup(int id, int progress, boolean fromUser) {
502            mId = id;
503            mProgress = progress;
504            mFromUser = fromUser;
505        }
506
507    }
508
509    private synchronized void doRefreshProgress(int id, int progress, boolean fromUser) {
510        float scale = mMax > 0 ? (float) progress / (float) mMax : 0;
511        final Drawable d = mCurrentDrawable;
512        if (d != null) {
513            Drawable progressDrawable = null;
514
515            if (d instanceof LayerDrawable) {
516                progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
517            }
518
519            final int level = (int) (scale * MAX_LEVEL);
520            (progressDrawable != null ? progressDrawable : d).setLevel(level);
521        } else {
522            invalidate();
523        }
524
525        if (id == R.id.progress) {
526            onProgressRefresh(scale, fromUser);
527        }
528    }
529
530    void onProgressRefresh(float scale, boolean fromUser) {
531    }
532
533    private synchronized void refreshProgress(int id, int progress, boolean fromUser) {
534        if (mUiThreadId == Thread.currentThread().getId()) {
535            doRefreshProgress(id, progress, fromUser);
536        } else {
537            RefreshProgressRunnable r;
538            if (mRefreshProgressRunnable != null) {
539                // Use cached RefreshProgressRunnable if available
540                r = mRefreshProgressRunnable;
541                // Uncache it
542                mRefreshProgressRunnable = null;
543                r.setup(id, progress, fromUser);
544            } else {
545                // Make a new one
546                r = new RefreshProgressRunnable(id, progress, fromUser);
547            }
548            post(r);
549        }
550    }
551
552    /**
553     * <p>Set the current progress to the specified value. Does not do anything
554     * if the progress bar is in indeterminate mode.</p>
555     *
556     * @param progress the new progress, between 0 and {@link #getMax()}
557     *
558     * @see #setIndeterminate(boolean)
559     * @see #isIndeterminate()
560     * @see #getProgress()
561     * @see #incrementProgressBy(int)
562     */
563    @android.view.RemotableViewMethod
564    public synchronized void setProgress(int progress) {
565        setProgress(progress, false);
566    }
567
568    @android.view.RemotableViewMethod
569    synchronized void setProgress(int progress, boolean fromUser) {
570        if (mIndeterminate) {
571            return;
572        }
573
574        if (progress < 0) {
575            progress = 0;
576        }
577
578        if (progress > mMax) {
579            progress = mMax;
580        }
581
582        if (progress != mProgress) {
583            mProgress = progress;
584            refreshProgress(R.id.progress, mProgress, fromUser);
585        }
586    }
587
588    /**
589     * <p>
590     * Set the current secondary progress to the specified value. Does not do
591     * anything if the progress bar is in indeterminate mode.
592     * </p>
593     *
594     * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()}
595     * @see #setIndeterminate(boolean)
596     * @see #isIndeterminate()
597     * @see #getSecondaryProgress()
598     * @see #incrementSecondaryProgressBy(int)
599     */
600    @android.view.RemotableViewMethod
601    public synchronized void setSecondaryProgress(int secondaryProgress) {
602        if (mIndeterminate) {
603            return;
604        }
605
606        if (secondaryProgress < 0) {
607            secondaryProgress = 0;
608        }
609
610        if (secondaryProgress > mMax) {
611            secondaryProgress = mMax;
612        }
613
614        if (secondaryProgress != mSecondaryProgress) {
615            mSecondaryProgress = secondaryProgress;
616            refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false);
617        }
618    }
619
620    /**
621     * <p>Get the progress bar's current level of progress. Return 0 when the
622     * progress bar is in indeterminate mode.</p>
623     *
624     * @return the current progress, between 0 and {@link #getMax()}
625     *
626     * @see #setIndeterminate(boolean)
627     * @see #isIndeterminate()
628     * @see #setProgress(int)
629     * @see #setMax(int)
630     * @see #getMax()
631     */
632    @ViewDebug.ExportedProperty(category = "progress")
633    public synchronized int getProgress() {
634        return mIndeterminate ? 0 : mProgress;
635    }
636
637    /**
638     * <p>Get the progress bar's current level of secondary progress. Return 0 when the
639     * progress bar is in indeterminate mode.</p>
640     *
641     * @return the current secondary progress, between 0 and {@link #getMax()}
642     *
643     * @see #setIndeterminate(boolean)
644     * @see #isIndeterminate()
645     * @see #setSecondaryProgress(int)
646     * @see #setMax(int)
647     * @see #getMax()
648     */
649    @ViewDebug.ExportedProperty(category = "progress")
650    public synchronized int getSecondaryProgress() {
651        return mIndeterminate ? 0 : mSecondaryProgress;
652    }
653
654    /**
655     * <p>Return the upper limit of this progress bar's range.</p>
656     *
657     * @return a positive integer
658     *
659     * @see #setMax(int)
660     * @see #getProgress()
661     * @see #getSecondaryProgress()
662     */
663    @ViewDebug.ExportedProperty(category = "progress")
664    public synchronized int getMax() {
665        return mMax;
666    }
667
668    /**
669     * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p>
670     *
671     * @param max the upper range of this progress bar
672     *
673     * @see #getMax()
674     * @see #setProgress(int)
675     * @see #setSecondaryProgress(int)
676     */
677    @android.view.RemotableViewMethod
678    public synchronized void setMax(int max) {
679        if (max < 0) {
680            max = 0;
681        }
682        if (max != mMax) {
683            mMax = max;
684            postInvalidate();
685
686            if (mProgress > max) {
687                mProgress = max;
688                refreshProgress(R.id.progress, mProgress, false);
689            }
690        }
691    }
692
693    /**
694     * <p>Increase the progress bar's progress by the specified amount.</p>
695     *
696     * @param diff the amount by which the progress must be increased
697     *
698     * @see #setProgress(int)
699     */
700    public synchronized final void incrementProgressBy(int diff) {
701        setProgress(mProgress + diff);
702    }
703
704    /**
705     * <p>Increase the progress bar's secondary progress by the specified amount.</p>
706     *
707     * @param diff the amount by which the secondary progress must be increased
708     *
709     * @see #setSecondaryProgress(int)
710     */
711    public synchronized final void incrementSecondaryProgressBy(int diff) {
712        setSecondaryProgress(mSecondaryProgress + diff);
713    }
714
715    /**
716     * <p>Start the indeterminate progress animation.</p>
717     */
718    void startAnimation() {
719        if (getVisibility() != VISIBLE) {
720            return;
721        }
722
723        if (mIndeterminateDrawable instanceof Animatable) {
724            mShouldStartAnimationDrawable = true;
725            mAnimation = null;
726        } else {
727            if (mInterpolator == null) {
728                mInterpolator = new LinearInterpolator();
729            }
730
731            mTransformation = new Transformation();
732            mAnimation = new AlphaAnimation(0.0f, 1.0f);
733            mAnimation.setRepeatMode(mBehavior);
734            mAnimation.setRepeatCount(Animation.INFINITE);
735            mAnimation.setDuration(mDuration);
736            mAnimation.setInterpolator(mInterpolator);
737            mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME);
738        }
739        postInvalidate();
740    }
741
742    /**
743     * <p>Stop the indeterminate progress animation.</p>
744     */
745    void stopAnimation() {
746        mAnimation = null;
747        mTransformation = null;
748        if (mIndeterminateDrawable instanceof Animatable) {
749            ((Animatable) mIndeterminateDrawable).stop();
750            mShouldStartAnimationDrawable = false;
751        }
752        postInvalidate();
753    }
754
755    /**
756     * Sets the acceleration curve for the indeterminate animation.
757     * The interpolator is loaded as a resource from the specified context.
758     *
759     * @param context The application environment
760     * @param resID The resource identifier of the interpolator to load
761     */
762    public void setInterpolator(Context context, int resID) {
763        setInterpolator(AnimationUtils.loadInterpolator(context, resID));
764    }
765
766    /**
767     * Sets the acceleration curve for the indeterminate animation.
768     * Defaults to a linear interpolation.
769     *
770     * @param interpolator The interpolator which defines the acceleration curve
771     */
772    public void setInterpolator(Interpolator interpolator) {
773        mInterpolator = interpolator;
774    }
775
776    /**
777     * Gets the acceleration curve type for the indeterminate animation.
778     *
779     * @return the {@link Interpolator} associated to this animation
780     */
781    public Interpolator getInterpolator() {
782        return mInterpolator;
783    }
784
785    @Override
786    @RemotableViewMethod
787    public void setVisibility(int v) {
788        if (getVisibility() != v) {
789            super.setVisibility(v);
790
791            if (mIndeterminate) {
792                // let's be nice with the UI thread
793                if (v == GONE || v == INVISIBLE) {
794                    stopAnimation();
795                } else {
796                    startAnimation();
797                }
798            }
799        }
800    }
801
802    @Override
803    protected void onVisibilityChanged(View changedView, int visibility) {
804        super.onVisibilityChanged(changedView, visibility);
805
806        if (mIndeterminate) {
807            // let's be nice with the UI thread
808            if (visibility == GONE || visibility == INVISIBLE) {
809                stopAnimation();
810            } else {
811                startAnimation();
812            }
813        }
814    }
815
816    @Override
817    public void invalidateDrawable(Drawable dr) {
818        if (!mInDrawing) {
819            if (verifyDrawable(dr)) {
820                final Rect dirty = dr.getBounds();
821                final int scrollX = mScrollX + mPaddingLeft;
822                final int scrollY = mScrollY + mPaddingTop;
823
824                invalidate(dirty.left + scrollX, dirty.top + scrollY,
825                        dirty.right + scrollX, dirty.bottom + scrollY);
826            } else {
827                super.invalidateDrawable(dr);
828            }
829        }
830    }
831
832    @Override
833    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
834        // onDraw will translate the canvas so we draw starting at 0,0
835        int right = w - mPaddingRight - mPaddingLeft;
836        int bottom = h - mPaddingBottom - mPaddingTop;
837
838        if (mIndeterminateDrawable != null) {
839            mIndeterminateDrawable.setBounds(0, 0, right, bottom);
840        }
841
842        if (mProgressDrawable != null) {
843            mProgressDrawable.setBounds(0, 0, right, bottom);
844        }
845    }
846
847    @Override
848    protected synchronized void onDraw(Canvas canvas) {
849        super.onDraw(canvas);
850
851        Drawable d = mCurrentDrawable;
852        if (d != null) {
853            // Translate canvas so a indeterminate circular progress bar with padding
854            // rotates properly in its animation
855            canvas.save();
856            canvas.translate(mPaddingLeft, mPaddingTop);
857            long time = getDrawingTime();
858            if (mAnimation != null) {
859                mAnimation.getTransformation(time, mTransformation);
860                float scale = mTransformation.getAlpha();
861                try {
862                    mInDrawing = true;
863                    d.setLevel((int) (scale * MAX_LEVEL));
864                } finally {
865                    mInDrawing = false;
866                }
867                if (SystemClock.uptimeMillis() - mLastDrawTime >= mAnimationResolution) {
868                    mLastDrawTime = SystemClock.uptimeMillis();
869                    postInvalidateDelayed(mAnimationResolution);
870                }
871            }
872            d.draw(canvas);
873            canvas.restore();
874            if (mShouldStartAnimationDrawable && d instanceof Animatable) {
875                ((Animatable) d).start();
876                mShouldStartAnimationDrawable = false;
877            }
878        }
879    }
880
881    @Override
882    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
883        Drawable d = mCurrentDrawable;
884
885        int dw = 0;
886        int dh = 0;
887        if (d != null) {
888            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
889            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
890        }
891        dw += mPaddingLeft + mPaddingRight;
892        dh += mPaddingTop + mPaddingBottom;
893
894        setMeasuredDimension(resolveSize(dw, widthMeasureSpec),
895                resolveSize(dh, heightMeasureSpec));
896    }
897
898    @Override
899    protected void drawableStateChanged() {
900        super.drawableStateChanged();
901
902        int[] state = getDrawableState();
903
904        if (mProgressDrawable != null && mProgressDrawable.isStateful()) {
905            mProgressDrawable.setState(state);
906        }
907
908        if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) {
909            mIndeterminateDrawable.setState(state);
910        }
911    }
912
913    static class SavedState extends BaseSavedState {
914        int progress;
915        int secondaryProgress;
916
917        /**
918         * Constructor called from {@link ProgressBar#onSaveInstanceState()}
919         */
920        SavedState(Parcelable superState) {
921            super(superState);
922        }
923
924        /**
925         * Constructor called from {@link #CREATOR}
926         */
927        private SavedState(Parcel in) {
928            super(in);
929            progress = in.readInt();
930            secondaryProgress = in.readInt();
931        }
932
933        @Override
934        public void writeToParcel(Parcel out, int flags) {
935            super.writeToParcel(out, flags);
936            out.writeInt(progress);
937            out.writeInt(secondaryProgress);
938        }
939
940        public static final Parcelable.Creator<SavedState> CREATOR
941                = new Parcelable.Creator<SavedState>() {
942            public SavedState createFromParcel(Parcel in) {
943                return new SavedState(in);
944            }
945
946            public SavedState[] newArray(int size) {
947                return new SavedState[size];
948            }
949        };
950    }
951
952    @Override
953    public Parcelable onSaveInstanceState() {
954        // Force our ancestor class to save its state
955        Parcelable superState = super.onSaveInstanceState();
956        SavedState ss = new SavedState(superState);
957
958        ss.progress = mProgress;
959        ss.secondaryProgress = mSecondaryProgress;
960
961        return ss;
962    }
963
964    @Override
965    public void onRestoreInstanceState(Parcelable state) {
966        SavedState ss = (SavedState) state;
967        super.onRestoreInstanceState(ss.getSuperState());
968
969        setProgress(ss.progress);
970        setSecondaryProgress(ss.secondaryProgress);
971    }
972
973    @Override
974    protected void onAttachedToWindow() {
975        super.onAttachedToWindow();
976        if (mIndeterminate) {
977            startAnimation();
978        }
979    }
980
981    @Override
982    protected void onDetachedFromWindow() {
983        super.onDetachedFromWindow();
984        if (mIndeterminate) {
985            stopAnimation();
986        }
987    }
988}
989