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