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