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