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.Pool; 42import android.util.Poolable; 43import android.util.PoolableManager; 44import android.util.Pools; 45import android.view.Gravity; 46import android.view.RemotableViewMethod; 47import android.view.View; 48import android.view.ViewDebug; 49import android.view.accessibility.AccessibilityEvent; 50import android.view.accessibility.AccessibilityManager; 51import android.view.accessibility.AccessibilityNodeInfo; 52import android.view.animation.AlphaAnimation; 53import android.view.animation.Animation; 54import android.view.animation.AnimationUtils; 55import android.view.animation.Interpolator; 56import android.view.animation.LinearInterpolator; 57import android.view.animation.Transformation; 58import android.widget.RemoteViews.RemoteView; 59 60import 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 195public 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 (mIndeterminateDrawable != null && canResolveLayoutDirection()) { 482 mIndeterminateDrawable.setLayoutDirection(getLayoutDirection()); 483 } 484 if (mIndeterminate) { 485 mCurrentDrawable = d; 486 postInvalidate(); 487 } 488 } 489 490 /** 491 * <p>Get the drawable used to draw the progress bar in 492 * progress mode.</p> 493 * 494 * @return a {@link android.graphics.drawable.Drawable} instance 495 * 496 * @see #setProgressDrawable(android.graphics.drawable.Drawable) 497 * @see #setIndeterminate(boolean) 498 */ 499 public Drawable getProgressDrawable() { 500 return mProgressDrawable; 501 } 502 503 /** 504 * <p>Define the drawable used to draw the progress bar in 505 * progress mode.</p> 506 * 507 * @param d the new drawable 508 * 509 * @see #getProgressDrawable() 510 * @see #setIndeterminate(boolean) 511 */ 512 public void setProgressDrawable(Drawable d) { 513 boolean needUpdate; 514 if (mProgressDrawable != null && d != mProgressDrawable) { 515 mProgressDrawable.setCallback(null); 516 needUpdate = true; 517 } else { 518 needUpdate = false; 519 } 520 521 if (d != null) { 522 d.setCallback(this); 523 if (canResolveLayoutDirection()) { 524 d.setLayoutDirection(getLayoutDirection()); 525 } 526 527 // Make sure the ProgressBar is always tall enough 528 int drawableHeight = d.getMinimumHeight(); 529 if (mMaxHeight < drawableHeight) { 530 mMaxHeight = drawableHeight; 531 requestLayout(); 532 } 533 } 534 mProgressDrawable = d; 535 if (!mIndeterminate) { 536 mCurrentDrawable = d; 537 postInvalidate(); 538 } 539 540 if (needUpdate) { 541 updateDrawableBounds(getWidth(), getHeight()); 542 updateDrawableState(); 543 doRefreshProgress(R.id.progress, mProgress, false, false); 544 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 545 } 546 } 547 548 /** 549 * @return The drawable currently used to draw the progress bar 550 */ 551 Drawable getCurrentDrawable() { 552 return mCurrentDrawable; 553 } 554 555 @Override 556 protected boolean verifyDrawable(Drawable who) { 557 return who == mProgressDrawable || who == mIndeterminateDrawable 558 || super.verifyDrawable(who); 559 } 560 561 @Override 562 public void jumpDrawablesToCurrentState() { 563 super.jumpDrawablesToCurrentState(); 564 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 565 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 566 } 567 568 /** 569 * @hide 570 */ 571 @Override 572 public void onResolveDrawables(int layoutDirection) { 573 final Drawable d = mCurrentDrawable; 574 if (d != null) { 575 d.setLayoutDirection(layoutDirection); 576 } 577 if (mIndeterminateDrawable != null) { 578 mIndeterminateDrawable.setLayoutDirection(layoutDirection); 579 } 580 if (mProgressDrawable != null) { 581 mProgressDrawable.setLayoutDirection(layoutDirection); 582 } 583 } 584 585 @Override 586 public void postInvalidate() { 587 if (!mNoInvalidate) { 588 super.postInvalidate(); 589 } 590 } 591 592 private class RefreshProgressRunnable implements Runnable { 593 public void run() { 594 synchronized (ProgressBar.this) { 595 final int count = mRefreshData.size(); 596 for (int i = 0; i < count; i++) { 597 final RefreshData rd = mRefreshData.get(i); 598 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); 599 rd.recycle(); 600 } 601 mRefreshData.clear(); 602 mRefreshIsPosted = false; 603 } 604 } 605 } 606 607 private static class RefreshData implements Poolable<RefreshData> { 608 public int id; 609 public int progress; 610 public boolean fromUser; 611 612 private RefreshData mNext; 613 private boolean mIsPooled; 614 615 private static final int POOL_MAX = 24; 616 private static final Pool<RefreshData> sPool = Pools.synchronizedPool( 617 Pools.finitePool(new PoolableManager<RefreshData>() { 618 @Override 619 public RefreshData newInstance() { 620 return new RefreshData(); 621 } 622 623 @Override 624 public void onAcquired(RefreshData element) { 625 } 626 627 @Override 628 public void onReleased(RefreshData element) { 629 } 630 }, POOL_MAX)); 631 632 public static RefreshData obtain(int id, int progress, boolean fromUser) { 633 RefreshData rd = sPool.acquire(); 634 rd.id = id; 635 rd.progress = progress; 636 rd.fromUser = fromUser; 637 return rd; 638 } 639 640 public void recycle() { 641 sPool.release(this); 642 } 643 644 @Override 645 public void setNextPoolable(RefreshData element) { 646 mNext = element; 647 } 648 649 @Override 650 public RefreshData getNextPoolable() { 651 return mNext; 652 } 653 654 @Override 655 public boolean isPooled() { 656 return mIsPooled; 657 } 658 659 @Override 660 public void setPooled(boolean isPooled) { 661 mIsPooled = isPooled; 662 } 663 } 664 665 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, 666 boolean callBackToApp) { 667 float scale = mMax > 0 ? (float) progress / (float) mMax : 0; 668 final Drawable d = mCurrentDrawable; 669 if (d != null) { 670 Drawable progressDrawable = null; 671 672 if (d instanceof LayerDrawable) { 673 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); 674 if (progressDrawable != null && canResolveLayoutDirection()) { 675 progressDrawable.setLayoutDirection(getLayoutDirection()); 676 } 677 } 678 679 final int level = (int) (scale * MAX_LEVEL); 680 (progressDrawable != null ? progressDrawable : d).setLevel(level); 681 } else { 682 invalidate(); 683 } 684 685 if (callBackToApp && id == R.id.progress) { 686 onProgressRefresh(scale, fromUser); 687 } 688 } 689 690 void onProgressRefresh(float scale, boolean fromUser) { 691 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 692 scheduleAccessibilityEventSender(); 693 } 694 } 695 696 private synchronized void refreshProgress(int id, int progress, boolean fromUser) { 697 if (mUiThreadId == Thread.currentThread().getId()) { 698 doRefreshProgress(id, progress, fromUser, true); 699 } else { 700 if (mRefreshProgressRunnable == null) { 701 mRefreshProgressRunnable = new RefreshProgressRunnable(); 702 } 703 704 final RefreshData rd = RefreshData.obtain(id, progress, fromUser); 705 mRefreshData.add(rd); 706 if (mAttached && !mRefreshIsPosted) { 707 post(mRefreshProgressRunnable); 708 mRefreshIsPosted = true; 709 } 710 } 711 } 712 713 /** 714 * <p>Set the current progress to the specified value. Does not do anything 715 * if the progress bar is in indeterminate mode.</p> 716 * 717 * @param progress the new progress, between 0 and {@link #getMax()} 718 * 719 * @see #setIndeterminate(boolean) 720 * @see #isIndeterminate() 721 * @see #getProgress() 722 * @see #incrementProgressBy(int) 723 */ 724 @android.view.RemotableViewMethod 725 public synchronized void setProgress(int progress) { 726 setProgress(progress, false); 727 } 728 729 @android.view.RemotableViewMethod 730 synchronized void setProgress(int progress, boolean fromUser) { 731 if (mIndeterminate) { 732 return; 733 } 734 735 if (progress < 0) { 736 progress = 0; 737 } 738 739 if (progress > mMax) { 740 progress = mMax; 741 } 742 743 if (progress != mProgress) { 744 mProgress = progress; 745 refreshProgress(R.id.progress, mProgress, fromUser); 746 } 747 } 748 749 /** 750 * <p> 751 * Set the current secondary progress to the specified value. Does not do 752 * anything if the progress bar is in indeterminate mode. 753 * </p> 754 * 755 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} 756 * @see #setIndeterminate(boolean) 757 * @see #isIndeterminate() 758 * @see #getSecondaryProgress() 759 * @see #incrementSecondaryProgressBy(int) 760 */ 761 @android.view.RemotableViewMethod 762 public synchronized void setSecondaryProgress(int secondaryProgress) { 763 if (mIndeterminate) { 764 return; 765 } 766 767 if (secondaryProgress < 0) { 768 secondaryProgress = 0; 769 } 770 771 if (secondaryProgress > mMax) { 772 secondaryProgress = mMax; 773 } 774 775 if (secondaryProgress != mSecondaryProgress) { 776 mSecondaryProgress = secondaryProgress; 777 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false); 778 } 779 } 780 781 /** 782 * <p>Get the progress bar's current level of progress. Return 0 when the 783 * progress bar is in indeterminate mode.</p> 784 * 785 * @return the current progress, between 0 and {@link #getMax()} 786 * 787 * @see #setIndeterminate(boolean) 788 * @see #isIndeterminate() 789 * @see #setProgress(int) 790 * @see #setMax(int) 791 * @see #getMax() 792 */ 793 @ViewDebug.ExportedProperty(category = "progress") 794 public synchronized int getProgress() { 795 return mIndeterminate ? 0 : mProgress; 796 } 797 798 /** 799 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 800 * progress bar is in indeterminate mode.</p> 801 * 802 * @return the current secondary progress, between 0 and {@link #getMax()} 803 * 804 * @see #setIndeterminate(boolean) 805 * @see #isIndeterminate() 806 * @see #setSecondaryProgress(int) 807 * @see #setMax(int) 808 * @see #getMax() 809 */ 810 @ViewDebug.ExportedProperty(category = "progress") 811 public synchronized int getSecondaryProgress() { 812 return mIndeterminate ? 0 : mSecondaryProgress; 813 } 814 815 /** 816 * <p>Return the upper limit of this progress bar's range.</p> 817 * 818 * @return a positive integer 819 * 820 * @see #setMax(int) 821 * @see #getProgress() 822 * @see #getSecondaryProgress() 823 */ 824 @ViewDebug.ExportedProperty(category = "progress") 825 public synchronized int getMax() { 826 return mMax; 827 } 828 829 /** 830 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p> 831 * 832 * @param max the upper range of this progress bar 833 * 834 * @see #getMax() 835 * @see #setProgress(int) 836 * @see #setSecondaryProgress(int) 837 */ 838 @android.view.RemotableViewMethod 839 public synchronized void setMax(int max) { 840 if (max < 0) { 841 max = 0; 842 } 843 if (max != mMax) { 844 mMax = max; 845 postInvalidate(); 846 847 if (mProgress > max) { 848 mProgress = max; 849 } 850 refreshProgress(R.id.progress, mProgress, false); 851 } 852 } 853 854 /** 855 * <p>Increase the progress bar's progress by the specified amount.</p> 856 * 857 * @param diff the amount by which the progress must be increased 858 * 859 * @see #setProgress(int) 860 */ 861 public synchronized final void incrementProgressBy(int diff) { 862 setProgress(mProgress + diff); 863 } 864 865 /** 866 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 867 * 868 * @param diff the amount by which the secondary progress must be increased 869 * 870 * @see #setSecondaryProgress(int) 871 */ 872 public synchronized final void incrementSecondaryProgressBy(int diff) { 873 setSecondaryProgress(mSecondaryProgress + diff); 874 } 875 876 /** 877 * <p>Start the indeterminate progress animation.</p> 878 */ 879 void startAnimation() { 880 if (getVisibility() != VISIBLE) { 881 return; 882 } 883 884 if (mIndeterminateDrawable instanceof Animatable) { 885 mShouldStartAnimationDrawable = true; 886 mHasAnimation = false; 887 } else { 888 mHasAnimation = true; 889 890 if (mInterpolator == null) { 891 mInterpolator = new LinearInterpolator(); 892 } 893 894 if (mTransformation == null) { 895 mTransformation = new Transformation(); 896 } else { 897 mTransformation.clear(); 898 } 899 900 if (mAnimation == null) { 901 mAnimation = new AlphaAnimation(0.0f, 1.0f); 902 } else { 903 mAnimation.reset(); 904 } 905 906 mAnimation.setRepeatMode(mBehavior); 907 mAnimation.setRepeatCount(Animation.INFINITE); 908 mAnimation.setDuration(mDuration); 909 mAnimation.setInterpolator(mInterpolator); 910 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 911 } 912 postInvalidate(); 913 } 914 915 /** 916 * <p>Stop the indeterminate progress animation.</p> 917 */ 918 void stopAnimation() { 919 mHasAnimation = false; 920 if (mIndeterminateDrawable instanceof Animatable) { 921 ((Animatable) mIndeterminateDrawable).stop(); 922 mShouldStartAnimationDrawable = false; 923 } 924 postInvalidate(); 925 } 926 927 /** 928 * Sets the acceleration curve for the indeterminate animation. 929 * The interpolator is loaded as a resource from the specified context. 930 * 931 * @param context The application environment 932 * @param resID The resource identifier of the interpolator to load 933 */ 934 public void setInterpolator(Context context, int resID) { 935 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 936 } 937 938 /** 939 * Sets the acceleration curve for the indeterminate animation. 940 * Defaults to a linear interpolation. 941 * 942 * @param interpolator The interpolator which defines the acceleration curve 943 */ 944 public void setInterpolator(Interpolator interpolator) { 945 mInterpolator = interpolator; 946 } 947 948 /** 949 * Gets the acceleration curve type for the indeterminate animation. 950 * 951 * @return the {@link Interpolator} associated to this animation 952 */ 953 public Interpolator getInterpolator() { 954 return mInterpolator; 955 } 956 957 @Override 958 @RemotableViewMethod 959 public void setVisibility(int v) { 960 if (getVisibility() != v) { 961 super.setVisibility(v); 962 963 if (mIndeterminate) { 964 // let's be nice with the UI thread 965 if (v == GONE || v == INVISIBLE) { 966 stopAnimation(); 967 } else { 968 startAnimation(); 969 } 970 } 971 } 972 } 973 974 @Override 975 protected void onVisibilityChanged(View changedView, int visibility) { 976 super.onVisibilityChanged(changedView, visibility); 977 978 if (mIndeterminate) { 979 // let's be nice with the UI thread 980 if (visibility == GONE || visibility == INVISIBLE) { 981 stopAnimation(); 982 } else { 983 startAnimation(); 984 } 985 } 986 } 987 988 @Override 989 public void invalidateDrawable(Drawable dr) { 990 if (!mInDrawing) { 991 if (verifyDrawable(dr)) { 992 final Rect dirty = dr.getBounds(); 993 final int scrollX = mScrollX + mPaddingLeft; 994 final int scrollY = mScrollY + mPaddingTop; 995 996 invalidate(dirty.left + scrollX, dirty.top + scrollY, 997 dirty.right + scrollX, dirty.bottom + scrollY); 998 } else { 999 super.invalidateDrawable(dr); 1000 } 1001 } 1002 } 1003 1004 @Override 1005 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1006 updateDrawableBounds(w, h); 1007 } 1008 1009 private void updateDrawableBounds(int w, int h) { 1010 // onDraw will translate the canvas so we draw starting at 0,0. 1011 // Subtract out padding for the purposes of the calculations below. 1012 w -= mPaddingRight + mPaddingLeft; 1013 h -= mPaddingTop + mPaddingBottom; 1014 1015 int right = w; 1016 int bottom = h; 1017 int top = 0; 1018 int left = 0; 1019 1020 if (mIndeterminateDrawable != null) { 1021 // Aspect ratio logic does not apply to AnimationDrawables 1022 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 1023 // Maintain aspect ratio. Certain kinds of animated drawables 1024 // get very confused otherwise. 1025 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 1026 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 1027 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 1028 final float boundAspect = (float) w / h; 1029 if (intrinsicAspect != boundAspect) { 1030 if (boundAspect > intrinsicAspect) { 1031 // New width is larger. Make it smaller to match height. 1032 final int width = (int) (h * intrinsicAspect); 1033 left = (w - width) / 2; 1034 right = left + width; 1035 } else { 1036 // New height is larger. Make it smaller to match width. 1037 final int height = (int) (w * (1 / intrinsicAspect)); 1038 top = (h - height) / 2; 1039 bottom = top + height; 1040 } 1041 } 1042 } 1043 if (isLayoutRtl()) { 1044 int tempLeft = left; 1045 left = w - right; 1046 right = w - tempLeft; 1047 } 1048 mIndeterminateDrawable.setBounds(left, top, right, bottom); 1049 } 1050 1051 if (mProgressDrawable != null) { 1052 mProgressDrawable.setBounds(0, 0, right, bottom); 1053 } 1054 } 1055 1056 @Override 1057 protected synchronized void onDraw(Canvas canvas) { 1058 super.onDraw(canvas); 1059 1060 Drawable d = mCurrentDrawable; 1061 if (d != null) { 1062 // Translate canvas so a indeterminate circular progress bar with padding 1063 // rotates properly in its animation 1064 canvas.save(); 1065 if(isLayoutRtl()) { 1066 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 1067 canvas.scale(-1.0f, 1.0f); 1068 } else { 1069 canvas.translate(mPaddingLeft, mPaddingTop); 1070 } 1071 long time = getDrawingTime(); 1072 if (mHasAnimation) { 1073 mAnimation.getTransformation(time, mTransformation); 1074 float scale = mTransformation.getAlpha(); 1075 try { 1076 mInDrawing = true; 1077 d.setLevel((int) (scale * MAX_LEVEL)); 1078 } finally { 1079 mInDrawing = false; 1080 } 1081 postInvalidateOnAnimation(); 1082 } 1083 d.draw(canvas); 1084 canvas.restore(); 1085 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 1086 ((Animatable) d).start(); 1087 mShouldStartAnimationDrawable = false; 1088 } 1089 } 1090 } 1091 1092 @Override 1093 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1094 Drawable d = mCurrentDrawable; 1095 1096 int dw = 0; 1097 int dh = 0; 1098 if (d != null) { 1099 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 1100 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 1101 } 1102 updateDrawableState(); 1103 dw += mPaddingLeft + mPaddingRight; 1104 dh += mPaddingTop + mPaddingBottom; 1105 1106 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), 1107 resolveSizeAndState(dh, heightMeasureSpec, 0)); 1108 } 1109 1110 @Override 1111 protected void drawableStateChanged() { 1112 super.drawableStateChanged(); 1113 updateDrawableState(); 1114 } 1115 1116 private void updateDrawableState() { 1117 int[] state = getDrawableState(); 1118 1119 if (mProgressDrawable != null && mProgressDrawable.isStateful()) { 1120 mProgressDrawable.setState(state); 1121 } 1122 1123 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { 1124 mIndeterminateDrawable.setState(state); 1125 } 1126 } 1127 1128 static class SavedState extends BaseSavedState { 1129 int progress; 1130 int secondaryProgress; 1131 1132 /** 1133 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 1134 */ 1135 SavedState(Parcelable superState) { 1136 super(superState); 1137 } 1138 1139 /** 1140 * Constructor called from {@link #CREATOR} 1141 */ 1142 private SavedState(Parcel in) { 1143 super(in); 1144 progress = in.readInt(); 1145 secondaryProgress = in.readInt(); 1146 } 1147 1148 @Override 1149 public void writeToParcel(Parcel out, int flags) { 1150 super.writeToParcel(out, flags); 1151 out.writeInt(progress); 1152 out.writeInt(secondaryProgress); 1153 } 1154 1155 public static final Parcelable.Creator<SavedState> CREATOR 1156 = new Parcelable.Creator<SavedState>() { 1157 public SavedState createFromParcel(Parcel in) { 1158 return new SavedState(in); 1159 } 1160 1161 public SavedState[] newArray(int size) { 1162 return new SavedState[size]; 1163 } 1164 }; 1165 } 1166 1167 @Override 1168 public Parcelable onSaveInstanceState() { 1169 // Force our ancestor class to save its state 1170 Parcelable superState = super.onSaveInstanceState(); 1171 SavedState ss = new SavedState(superState); 1172 1173 ss.progress = mProgress; 1174 ss.secondaryProgress = mSecondaryProgress; 1175 1176 return ss; 1177 } 1178 1179 @Override 1180 public void onRestoreInstanceState(Parcelable state) { 1181 SavedState ss = (SavedState) state; 1182 super.onRestoreInstanceState(ss.getSuperState()); 1183 1184 setProgress(ss.progress); 1185 setSecondaryProgress(ss.secondaryProgress); 1186 } 1187 1188 @Override 1189 protected void onAttachedToWindow() { 1190 super.onAttachedToWindow(); 1191 if (mIndeterminate) { 1192 startAnimation(); 1193 } 1194 if (mRefreshData != null) { 1195 synchronized (this) { 1196 final int count = mRefreshData.size(); 1197 for (int i = 0; i < count; i++) { 1198 final RefreshData rd = mRefreshData.get(i); 1199 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); 1200 rd.recycle(); 1201 } 1202 mRefreshData.clear(); 1203 } 1204 } 1205 mAttached = true; 1206 } 1207 1208 @Override 1209 protected void onDetachedFromWindow() { 1210 if (mIndeterminate) { 1211 stopAnimation(); 1212 } 1213 if (mRefreshProgressRunnable != null) { 1214 removeCallbacks(mRefreshProgressRunnable); 1215 } 1216 if (mRefreshProgressRunnable != null && mRefreshIsPosted) { 1217 removeCallbacks(mRefreshProgressRunnable); 1218 } 1219 if (mAccessibilityEventSender != null) { 1220 removeCallbacks(mAccessibilityEventSender); 1221 } 1222 // This should come after stopAnimation(), otherwise an invalidate message remains in the 1223 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 1224 super.onDetachedFromWindow(); 1225 mAttached = false; 1226 } 1227 1228 @Override 1229 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1230 super.onInitializeAccessibilityEvent(event); 1231 event.setClassName(ProgressBar.class.getName()); 1232 event.setItemCount(mMax); 1233 event.setCurrentItemIndex(mProgress); 1234 } 1235 1236 @Override 1237 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1238 super.onInitializeAccessibilityNodeInfo(info); 1239 info.setClassName(ProgressBar.class.getName()); 1240 } 1241 1242 /** 1243 * Schedule a command for sending an accessibility event. 1244 * </br> 1245 * Note: A command is used to ensure that accessibility events 1246 * are sent at most one in a given time frame to save 1247 * system resources while the progress changes quickly. 1248 */ 1249 private void scheduleAccessibilityEventSender() { 1250 if (mAccessibilityEventSender == null) { 1251 mAccessibilityEventSender = new AccessibilityEventSender(); 1252 } else { 1253 removeCallbacks(mAccessibilityEventSender); 1254 } 1255 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 1256 } 1257 1258 /** 1259 * Command for sending an accessibility event. 1260 */ 1261 private class AccessibilityEventSender implements Runnable { 1262 public void run() { 1263 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1264 } 1265 } 1266} 1267