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