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