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