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