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