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