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