ProgressBar.java revision e785d02197a6f0b3ca837f6aff781601ebf5fd59
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_mirrorForRtl 188 * @attr ref android.R.styleable#ProgressBar_progress 189 * @attr ref android.R.styleable#ProgressBar_progressDrawable 190 * @attr ref android.R.styleable#ProgressBar_secondaryProgress 191 */ 192@RemoteView 193public class ProgressBar extends View { 194 private static final int MAX_LEVEL = 10000; 195 private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200; 196 197 int mMinWidth; 198 int mMaxWidth; 199 int mMinHeight; 200 int mMaxHeight; 201 202 private int mProgress; 203 private int mSecondaryProgress; 204 private int mMax; 205 206 private int mBehavior; 207 private int mDuration; 208 private boolean mIndeterminate; 209 private boolean mOnlyIndeterminate; 210 private Transformation mTransformation; 211 private AlphaAnimation mAnimation; 212 private boolean mHasAnimation; 213 private Drawable mIndeterminateDrawable; 214 private Drawable mProgressDrawable; 215 private Drawable mCurrentDrawable; 216 Bitmap mSampleTile; 217 private boolean mNoInvalidate; 218 private Interpolator mInterpolator; 219 private RefreshProgressRunnable mRefreshProgressRunnable; 220 private long mUiThreadId; 221 private boolean mShouldStartAnimationDrawable; 222 223 private boolean mInDrawing; 224 private boolean mAttached; 225 private boolean mRefreshIsPosted; 226 227 boolean mMirrorForRtl = false; 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 defStyleAttr) { 246 this(context, attrs, defStyleAttr, 0); 247 } 248 249 public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 250 super(context, attrs, defStyleAttr, defStyleRes); 251 252 mUiThreadId = Thread.currentThread().getId(); 253 initProgressBar(); 254 255 final TypedArray a = context.obtainStyledAttributes( 256 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); 257 258 mNoInvalidate = true; 259 260 Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); 261 if (drawable != null) { 262 // Calling this method can set mMaxHeight, make sure the corresponding 263 // XML attribute for mMaxHeight is read after calling this method 264 setProgressDrawableTiled(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 setIndeterminateDrawableTiled(drawable); 294 } 295 296 mOnlyIndeterminate = a.getBoolean( 297 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate); 298 299 mNoInvalidate = false; 300 301 setIndeterminate(mOnlyIndeterminate || a.getBoolean( 302 R.styleable.ProgressBar_indeterminate, mIndeterminate)); 303 304 mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); 305 306 a.recycle(); 307 } 308 309 /** 310 * Converts a drawable to a tiled version of itself. It will recursively 311 * traverse layer and state list drawables. 312 */ 313 private Drawable tileify(Drawable drawable, boolean clip) { 314 315 if (drawable instanceof LayerDrawable) { 316 LayerDrawable background = (LayerDrawable) drawable; 317 final int N = background.getNumberOfLayers(); 318 Drawable[] outDrawables = new Drawable[N]; 319 320 for (int i = 0; i < N; i++) { 321 int id = background.getId(i); 322 outDrawables[i] = tileify(background.getDrawable(i), 323 (id == R.id.progress || id == R.id.secondaryProgress)); 324 } 325 326 LayerDrawable newBg = new LayerDrawable(outDrawables); 327 328 for (int i = 0; i < N; i++) { 329 newBg.setId(i, background.getId(i)); 330 } 331 332 return newBg; 333 334 } else if (drawable instanceof StateListDrawable) { 335 StateListDrawable in = (StateListDrawable) drawable; 336 StateListDrawable out = new StateListDrawable(); 337 int numStates = in.getStateCount(); 338 for (int i = 0; i < numStates; i++) { 339 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); 340 } 341 return out; 342 343 } else if (drawable instanceof BitmapDrawable) { 344 final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); 345 if (mSampleTile == null) { 346 mSampleTile = tileBitmap; 347 } 348 349 final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); 350 351 final BitmapShader bitmapShader = new BitmapShader(tileBitmap, 352 Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); 353 shapeDrawable.getPaint().setShader(bitmapShader); 354 355 return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, 356 ClipDrawable.HORIZONTAL) : shapeDrawable; 357 } 358 359 return drawable; 360 } 361 362 Shape getDrawableShape() { 363 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; 364 return new RoundRectShape(roundedCorners, null, null); 365 } 366 367 /** 368 * Convert a AnimationDrawable for use as a barberpole animation. 369 * Each frame of the animation is wrapped in a ClipDrawable and 370 * given a tiling BitmapShader. 371 */ 372 private Drawable tileifyIndeterminate(Drawable drawable) { 373 if (drawable instanceof AnimationDrawable) { 374 AnimationDrawable background = (AnimationDrawable) drawable; 375 final int N = background.getNumberOfFrames(); 376 AnimationDrawable newBg = new AnimationDrawable(); 377 newBg.setOneShot(background.isOneShot()); 378 379 for (int i = 0; i < N; i++) { 380 Drawable frame = tileify(background.getFrame(i), true); 381 frame.setLevel(10000); 382 newBg.addFrame(frame, background.getDuration(i)); 383 } 384 newBg.setLevel(10000); 385 drawable = newBg; 386 } 387 return drawable; 388 } 389 390 /** 391 * <p> 392 * Initialize the progress bar's default values: 393 * </p> 394 * <ul> 395 * <li>progress = 0</li> 396 * <li>max = 100</li> 397 * <li>animation duration = 4000 ms</li> 398 * <li>indeterminate = false</li> 399 * <li>behavior = repeat</li> 400 * </ul> 401 */ 402 private void initProgressBar() { 403 mMax = 100; 404 mProgress = 0; 405 mSecondaryProgress = 0; 406 mIndeterminate = false; 407 mOnlyIndeterminate = false; 408 mDuration = 4000; 409 mBehavior = AlphaAnimation.RESTART; 410 mMinWidth = 24; 411 mMaxWidth = 48; 412 mMinHeight = 24; 413 mMaxHeight = 48; 414 } 415 416 /** 417 * <p>Indicate whether this progress bar is in indeterminate mode.</p> 418 * 419 * @return true if the progress bar is in indeterminate mode 420 */ 421 @ViewDebug.ExportedProperty(category = "progress") 422 public synchronized boolean isIndeterminate() { 423 return mIndeterminate; 424 } 425 426 /** 427 * <p>Change the indeterminate mode for this progress bar. In indeterminate 428 * mode, the progress is ignored and the progress bar shows an infinite 429 * animation instead.</p> 430 * 431 * If this progress bar's style only supports indeterminate mode (such as the circular 432 * progress bars), then this will be ignored. 433 * 434 * @param indeterminate true to enable the indeterminate mode 435 */ 436 @android.view.RemotableViewMethod 437 public synchronized void setIndeterminate(boolean indeterminate) { 438 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { 439 mIndeterminate = indeterminate; 440 441 if (indeterminate) { 442 // swap between indeterminate and regular backgrounds 443 mCurrentDrawable = mIndeterminateDrawable; 444 startAnimation(); 445 } else { 446 mCurrentDrawable = mProgressDrawable; 447 stopAnimation(); 448 } 449 } 450 } 451 452 /** 453 * <p>Get the drawable used to draw the progress bar in 454 * indeterminate mode.</p> 455 * 456 * @return a {@link android.graphics.drawable.Drawable} instance 457 * 458 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) 459 * @see #setIndeterminate(boolean) 460 */ 461 public Drawable getIndeterminateDrawable() { 462 return mIndeterminateDrawable; 463 } 464 465 /** 466 * Define the drawable used to draw the progress bar in indeterminate mode. 467 * 468 * @param d the new drawable 469 * @see #getIndeterminateDrawable() 470 * @see #setIndeterminate(boolean) 471 */ 472 public void setIndeterminateDrawable(Drawable d) { 473 if (d != null) { 474 d.setCallback(this); 475 } 476 mIndeterminateDrawable = d; 477 if (mIndeterminateDrawable != null && canResolveLayoutDirection()) { 478 mIndeterminateDrawable.setLayoutDirection(getLayoutDirection()); 479 } 480 if (mIndeterminate) { 481 mCurrentDrawable = d; 482 postInvalidate(); 483 } 484 } 485 486 /** 487 * Define the tileable drawable used to draw the progress bar in 488 * indeterminate mode. 489 * <p> 490 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 491 * tiled copy will be generated for display as a progress bar. 492 * 493 * @param d the new drawable 494 * @see #getIndeterminateDrawable() 495 * @see #setIndeterminate(boolean) 496 */ 497 public void setIndeterminateDrawableTiled(Drawable d) { 498 if (d != null) { 499 d = tileifyIndeterminate(d); 500 } 501 502 setIndeterminateDrawable(d); 503 } 504 505 /** 506 * <p>Get the drawable used to draw the progress bar in 507 * progress mode.</p> 508 * 509 * @return a {@link android.graphics.drawable.Drawable} instance 510 * 511 * @see #setProgressDrawable(android.graphics.drawable.Drawable) 512 * @see #setIndeterminate(boolean) 513 */ 514 public Drawable getProgressDrawable() { 515 return mProgressDrawable; 516 } 517 518 /** 519 * Define the drawable used to draw the progress bar in progress mode. 520 * 521 * @param d the new drawable 522 * @see #getProgressDrawable() 523 * @see #setIndeterminate(boolean) 524 */ 525 public void setProgressDrawable(Drawable d) { 526 boolean needUpdate; 527 if (mProgressDrawable != null && d != mProgressDrawable) { 528 mProgressDrawable.setCallback(null); 529 needUpdate = true; 530 } else { 531 needUpdate = false; 532 } 533 534 if (d != null) { 535 d.setCallback(this); 536 if (canResolveLayoutDirection()) { 537 d.setLayoutDirection(getLayoutDirection()); 538 } 539 540 // Make sure the ProgressBar is always tall enough 541 int drawableHeight = d.getMinimumHeight(); 542 if (mMaxHeight < drawableHeight) { 543 mMaxHeight = drawableHeight; 544 requestLayout(); 545 } 546 } 547 mProgressDrawable = d; 548 if (!mIndeterminate) { 549 mCurrentDrawable = d; 550 postInvalidate(); 551 } 552 553 if (needUpdate) { 554 updateDrawableBounds(getWidth(), getHeight()); 555 updateDrawableState(); 556 doRefreshProgress(R.id.progress, mProgress, false, false); 557 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 558 } 559 } 560 561 /** 562 * Define the tileable drawable used to draw the progress bar in 563 * progress mode. 564 * <p> 565 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 566 * tiled copy will be generated for display as a progress bar. 567 * 568 * @param d the new drawable 569 * @see #getProgressDrawable() 570 * @see #setIndeterminate(boolean) 571 */ 572 public void setProgressDrawableTiled(Drawable d) { 573 if (d != null) { 574 d = tileify(d, false); 575 } 576 577 setProgressDrawable(d); 578 } 579 580 /** 581 * @return The drawable currently used to draw the progress bar 582 */ 583 Drawable getCurrentDrawable() { 584 return mCurrentDrawable; 585 } 586 587 @Override 588 protected boolean verifyDrawable(Drawable who) { 589 return who == mProgressDrawable || who == mIndeterminateDrawable 590 || super.verifyDrawable(who); 591 } 592 593 @Override 594 public void jumpDrawablesToCurrentState() { 595 super.jumpDrawablesToCurrentState(); 596 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 597 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 598 } 599 600 /** 601 * @hide 602 */ 603 @Override 604 public void onResolveDrawables(int layoutDirection) { 605 final Drawable d = mCurrentDrawable; 606 if (d != null) { 607 d.setLayoutDirection(layoutDirection); 608 } 609 if (mIndeterminateDrawable != null) { 610 mIndeterminateDrawable.setLayoutDirection(layoutDirection); 611 } 612 if (mProgressDrawable != null) { 613 mProgressDrawable.setLayoutDirection(layoutDirection); 614 } 615 } 616 617 @Override 618 public void postInvalidate() { 619 if (!mNoInvalidate) { 620 super.postInvalidate(); 621 } 622 } 623 624 private class RefreshProgressRunnable implements Runnable { 625 public void run() { 626 synchronized (ProgressBar.this) { 627 final int count = mRefreshData.size(); 628 for (int i = 0; i < count; i++) { 629 final RefreshData rd = mRefreshData.get(i); 630 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); 631 rd.recycle(); 632 } 633 mRefreshData.clear(); 634 mRefreshIsPosted = false; 635 } 636 } 637 } 638 639 private static class RefreshData { 640 private static final int POOL_MAX = 24; 641 private static final SynchronizedPool<RefreshData> sPool = 642 new SynchronizedPool<RefreshData>(POOL_MAX); 643 644 public int id; 645 public int progress; 646 public boolean fromUser; 647 648 public static RefreshData obtain(int id, int progress, boolean fromUser) { 649 RefreshData rd = sPool.acquire(); 650 if (rd == null) { 651 rd = new RefreshData(); 652 } 653 rd.id = id; 654 rd.progress = progress; 655 rd.fromUser = fromUser; 656 return rd; 657 } 658 659 public void recycle() { 660 sPool.release(this); 661 } 662 } 663 664 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, 665 boolean callBackToApp) { 666 float scale = mMax > 0 ? (float) progress / (float) mMax : 0; 667 final Drawable d = mCurrentDrawable; 668 if (d != null) { 669 Drawable progressDrawable = null; 670 671 if (d instanceof LayerDrawable) { 672 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); 673 if (progressDrawable != null && canResolveLayoutDirection()) { 674 progressDrawable.setLayoutDirection(getLayoutDirection()); 675 } 676 } 677 678 final int level = (int) (scale * MAX_LEVEL); 679 (progressDrawable != null ? progressDrawable : d).setLevel(level); 680 } else { 681 invalidate(); 682 } 683 684 if (callBackToApp && id == R.id.progress) { 685 onProgressRefresh(scale, fromUser); 686 } 687 } 688 689 void onProgressRefresh(float scale, boolean fromUser) { 690 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 691 scheduleAccessibilityEventSender(); 692 } 693 } 694 695 private synchronized void refreshProgress(int id, int progress, boolean fromUser) { 696 if (mUiThreadId == Thread.currentThread().getId()) { 697 doRefreshProgress(id, progress, fromUser, true); 698 } else { 699 if (mRefreshProgressRunnable == null) { 700 mRefreshProgressRunnable = new RefreshProgressRunnable(); 701 } 702 703 final RefreshData rd = RefreshData.obtain(id, progress, fromUser); 704 mRefreshData.add(rd); 705 if (mAttached && !mRefreshIsPosted) { 706 post(mRefreshProgressRunnable); 707 mRefreshIsPosted = true; 708 } 709 } 710 } 711 712 /** 713 * <p>Set the current progress to the specified value. Does not do anything 714 * if the progress bar is in indeterminate mode.</p> 715 * 716 * @param progress the new progress, between 0 and {@link #getMax()} 717 * 718 * @see #setIndeterminate(boolean) 719 * @see #isIndeterminate() 720 * @see #getProgress() 721 * @see #incrementProgressBy(int) 722 */ 723 @android.view.RemotableViewMethod 724 public synchronized void setProgress(int progress) { 725 setProgress(progress, false); 726 } 727 728 @android.view.RemotableViewMethod 729 synchronized void setProgress(int progress, boolean fromUser) { 730 if (mIndeterminate) { 731 return; 732 } 733 734 if (progress < 0) { 735 progress = 0; 736 } 737 738 if (progress > mMax) { 739 progress = mMax; 740 } 741 742 if (progress != mProgress) { 743 mProgress = progress; 744 refreshProgress(R.id.progress, mProgress, fromUser); 745 } 746 } 747 748 /** 749 * <p> 750 * Set the current secondary progress to the specified value. Does not do 751 * anything if the progress bar is in indeterminate mode. 752 * </p> 753 * 754 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} 755 * @see #setIndeterminate(boolean) 756 * @see #isIndeterminate() 757 * @see #getSecondaryProgress() 758 * @see #incrementSecondaryProgressBy(int) 759 */ 760 @android.view.RemotableViewMethod 761 public synchronized void setSecondaryProgress(int secondaryProgress) { 762 if (mIndeterminate) { 763 return; 764 } 765 766 if (secondaryProgress < 0) { 767 secondaryProgress = 0; 768 } 769 770 if (secondaryProgress > mMax) { 771 secondaryProgress = mMax; 772 } 773 774 if (secondaryProgress != mSecondaryProgress) { 775 mSecondaryProgress = secondaryProgress; 776 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false); 777 } 778 } 779 780 /** 781 * <p>Get the progress bar's current level of progress. Return 0 when the 782 * progress bar is in indeterminate mode.</p> 783 * 784 * @return the current progress, between 0 and {@link #getMax()} 785 * 786 * @see #setIndeterminate(boolean) 787 * @see #isIndeterminate() 788 * @see #setProgress(int) 789 * @see #setMax(int) 790 * @see #getMax() 791 */ 792 @ViewDebug.ExportedProperty(category = "progress") 793 public synchronized int getProgress() { 794 return mIndeterminate ? 0 : mProgress; 795 } 796 797 /** 798 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 799 * progress bar is in indeterminate mode.</p> 800 * 801 * @return the current secondary progress, between 0 and {@link #getMax()} 802 * 803 * @see #setIndeterminate(boolean) 804 * @see #isIndeterminate() 805 * @see #setSecondaryProgress(int) 806 * @see #setMax(int) 807 * @see #getMax() 808 */ 809 @ViewDebug.ExportedProperty(category = "progress") 810 public synchronized int getSecondaryProgress() { 811 return mIndeterminate ? 0 : mSecondaryProgress; 812 } 813 814 /** 815 * <p>Return the upper limit of this progress bar's range.</p> 816 * 817 * @return a positive integer 818 * 819 * @see #setMax(int) 820 * @see #getProgress() 821 * @see #getSecondaryProgress() 822 */ 823 @ViewDebug.ExportedProperty(category = "progress") 824 public synchronized int getMax() { 825 return mMax; 826 } 827 828 /** 829 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p> 830 * 831 * @param max the upper range of this progress bar 832 * 833 * @see #getMax() 834 * @see #setProgress(int) 835 * @see #setSecondaryProgress(int) 836 */ 837 @android.view.RemotableViewMethod 838 public synchronized void setMax(int max) { 839 if (max < 0) { 840 max = 0; 841 } 842 if (max != mMax) { 843 mMax = max; 844 postInvalidate(); 845 846 if (mProgress > max) { 847 mProgress = max; 848 } 849 refreshProgress(R.id.progress, mProgress, false); 850 } 851 } 852 853 /** 854 * <p>Increase the progress bar's progress by the specified amount.</p> 855 * 856 * @param diff the amount by which the progress must be increased 857 * 858 * @see #setProgress(int) 859 */ 860 public synchronized final void incrementProgressBy(int diff) { 861 setProgress(mProgress + diff); 862 } 863 864 /** 865 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 866 * 867 * @param diff the amount by which the secondary progress must be increased 868 * 869 * @see #setSecondaryProgress(int) 870 */ 871 public synchronized final void incrementSecondaryProgressBy(int diff) { 872 setSecondaryProgress(mSecondaryProgress + diff); 873 } 874 875 /** 876 * <p>Start the indeterminate progress animation.</p> 877 */ 878 void startAnimation() { 879 if (getVisibility() != VISIBLE) { 880 return; 881 } 882 883 if (mIndeterminateDrawable instanceof Animatable) { 884 mShouldStartAnimationDrawable = true; 885 mHasAnimation = false; 886 } else { 887 mHasAnimation = true; 888 889 if (mInterpolator == null) { 890 mInterpolator = new LinearInterpolator(); 891 } 892 893 if (mTransformation == null) { 894 mTransformation = new Transformation(); 895 } else { 896 mTransformation.clear(); 897 } 898 899 if (mAnimation == null) { 900 mAnimation = new AlphaAnimation(0.0f, 1.0f); 901 } else { 902 mAnimation.reset(); 903 } 904 905 mAnimation.setRepeatMode(mBehavior); 906 mAnimation.setRepeatCount(Animation.INFINITE); 907 mAnimation.setDuration(mDuration); 908 mAnimation.setInterpolator(mInterpolator); 909 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 910 } 911 postInvalidate(); 912 } 913 914 /** 915 * <p>Stop the indeterminate progress animation.</p> 916 */ 917 void stopAnimation() { 918 mHasAnimation = false; 919 if (mIndeterminateDrawable instanceof Animatable) { 920 ((Animatable) mIndeterminateDrawable).stop(); 921 mShouldStartAnimationDrawable = false; 922 } 923 postInvalidate(); 924 } 925 926 /** 927 * Sets the acceleration curve for the indeterminate animation. 928 * The interpolator is loaded as a resource from the specified context. 929 * 930 * @param context The application environment 931 * @param resID The resource identifier of the interpolator to load 932 */ 933 public void setInterpolator(Context context, int resID) { 934 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 935 } 936 937 /** 938 * Sets the acceleration curve for the indeterminate animation. 939 * Defaults to a linear interpolation. 940 * 941 * @param interpolator The interpolator which defines the acceleration curve 942 */ 943 public void setInterpolator(Interpolator interpolator) { 944 mInterpolator = interpolator; 945 } 946 947 /** 948 * Gets the acceleration curve type for the indeterminate animation. 949 * 950 * @return the {@link Interpolator} associated to this animation 951 */ 952 public Interpolator getInterpolator() { 953 return mInterpolator; 954 } 955 956 @Override 957 @RemotableViewMethod 958 public void setVisibility(int v) { 959 if (getVisibility() != v) { 960 super.setVisibility(v); 961 962 if (mIndeterminate) { 963 // let's be nice with the UI thread 964 if (v == GONE || v == INVISIBLE) { 965 stopAnimation(); 966 } else { 967 startAnimation(); 968 } 969 } 970 } 971 } 972 973 @Override 974 protected void onVisibilityChanged(View changedView, int visibility) { 975 super.onVisibilityChanged(changedView, visibility); 976 977 if (mIndeterminate) { 978 // let's be nice with the UI thread 979 if (visibility == GONE || visibility == INVISIBLE) { 980 stopAnimation(); 981 } else { 982 startAnimation(); 983 } 984 } 985 } 986 987 @Override 988 public void invalidateDrawable(Drawable dr) { 989 if (!mInDrawing) { 990 if (verifyDrawable(dr)) { 991 final Rect dirty = dr.getBounds(); 992 993 invalidate(dirty.left + mScrollX, dirty.top + mScrollY, 994 dirty.right + mScrollX, dirty.bottom + mScrollY); 995 } else { 996 super.invalidateDrawable(dr); 997 } 998 } 999 } 1000 1001 @Override 1002 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1003 updateDrawableBounds(w, h); 1004 } 1005 1006 private void updateDrawableBounds(int w, int h) { 1007 // onDraw will translate the canvas so we draw starting at 0,0. 1008 // Subtract out padding for the purposes of the calculations below. 1009 w -= mPaddingRight + mPaddingLeft; 1010 h -= mPaddingTop + mPaddingBottom; 1011 1012 int right = w; 1013 int bottom = h; 1014 int top = 0; 1015 int left = 0; 1016 1017 if (mIndeterminateDrawable != null) { 1018 // Aspect ratio logic does not apply to AnimationDrawables 1019 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 1020 // Maintain aspect ratio. Certain kinds of animated drawables 1021 // get very confused otherwise. 1022 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 1023 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 1024 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 1025 final float boundAspect = (float) w / h; 1026 if (intrinsicAspect != boundAspect) { 1027 if (boundAspect > intrinsicAspect) { 1028 // New width is larger. Make it smaller to match height. 1029 final int width = (int) (h * intrinsicAspect); 1030 left = (w - width) / 2; 1031 right = left + width; 1032 } else { 1033 // New height is larger. Make it smaller to match width. 1034 final int height = (int) (w * (1 / intrinsicAspect)); 1035 top = (h - height) / 2; 1036 bottom = top + height; 1037 } 1038 } 1039 } 1040 if (isLayoutRtl() && mMirrorForRtl) { 1041 int tempLeft = left; 1042 left = w - right; 1043 right = w - tempLeft; 1044 } 1045 mIndeterminateDrawable.setBounds(left, top, right, bottom); 1046 } 1047 1048 if (mProgressDrawable != null) { 1049 mProgressDrawable.setBounds(0, 0, right, bottom); 1050 } 1051 } 1052 1053 @Override 1054 protected synchronized void onDraw(Canvas canvas) { 1055 super.onDraw(canvas); 1056 1057 Drawable d = mCurrentDrawable; 1058 if (d != null) { 1059 // Translate canvas so a indeterminate circular progress bar with padding 1060 // rotates properly in its animation 1061 canvas.save(); 1062 if(isLayoutRtl() && mMirrorForRtl) { 1063 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 1064 canvas.scale(-1.0f, 1.0f); 1065 } else { 1066 canvas.translate(mPaddingLeft, mPaddingTop); 1067 } 1068 long time = getDrawingTime(); 1069 if (mHasAnimation) { 1070 mAnimation.getTransformation(time, mTransformation); 1071 float scale = mTransformation.getAlpha(); 1072 try { 1073 mInDrawing = true; 1074 d.setLevel((int) (scale * MAX_LEVEL)); 1075 } finally { 1076 mInDrawing = false; 1077 } 1078 postInvalidateOnAnimation(); 1079 } 1080 d.draw(canvas); 1081 canvas.restore(); 1082 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 1083 ((Animatable) d).start(); 1084 mShouldStartAnimationDrawable = false; 1085 } 1086 } 1087 } 1088 1089 @Override 1090 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1091 Drawable d = mCurrentDrawable; 1092 1093 int dw = 0; 1094 int dh = 0; 1095 if (d != null) { 1096 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 1097 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 1098 } 1099 updateDrawableState(); 1100 dw += mPaddingLeft + mPaddingRight; 1101 dh += mPaddingTop + mPaddingBottom; 1102 1103 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), 1104 resolveSizeAndState(dh, heightMeasureSpec, 0)); 1105 } 1106 1107 @Override 1108 protected void drawableStateChanged() { 1109 super.drawableStateChanged(); 1110 updateDrawableState(); 1111 } 1112 1113 private void updateDrawableState() { 1114 int[] state = getDrawableState(); 1115 1116 if (mProgressDrawable != null && mProgressDrawable.isStateful()) { 1117 mProgressDrawable.setState(state); 1118 } 1119 1120 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { 1121 mIndeterminateDrawable.setState(state); 1122 } 1123 } 1124 1125 static class SavedState extends BaseSavedState { 1126 int progress; 1127 int secondaryProgress; 1128 1129 /** 1130 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 1131 */ 1132 SavedState(Parcelable superState) { 1133 super(superState); 1134 } 1135 1136 /** 1137 * Constructor called from {@link #CREATOR} 1138 */ 1139 private SavedState(Parcel in) { 1140 super(in); 1141 progress = in.readInt(); 1142 secondaryProgress = in.readInt(); 1143 } 1144 1145 @Override 1146 public void writeToParcel(Parcel out, int flags) { 1147 super.writeToParcel(out, flags); 1148 out.writeInt(progress); 1149 out.writeInt(secondaryProgress); 1150 } 1151 1152 public static final Parcelable.Creator<SavedState> CREATOR 1153 = new Parcelable.Creator<SavedState>() { 1154 public SavedState createFromParcel(Parcel in) { 1155 return new SavedState(in); 1156 } 1157 1158 public SavedState[] newArray(int size) { 1159 return new SavedState[size]; 1160 } 1161 }; 1162 } 1163 1164 @Override 1165 public Parcelable onSaveInstanceState() { 1166 // Force our ancestor class to save its state 1167 Parcelable superState = super.onSaveInstanceState(); 1168 SavedState ss = new SavedState(superState); 1169 1170 ss.progress = mProgress; 1171 ss.secondaryProgress = mSecondaryProgress; 1172 1173 return ss; 1174 } 1175 1176 @Override 1177 public void onRestoreInstanceState(Parcelable state) { 1178 SavedState ss = (SavedState) state; 1179 super.onRestoreInstanceState(ss.getSuperState()); 1180 1181 setProgress(ss.progress); 1182 setSecondaryProgress(ss.secondaryProgress); 1183 } 1184 1185 @Override 1186 protected void onAttachedToWindow() { 1187 super.onAttachedToWindow(); 1188 if (mIndeterminate) { 1189 startAnimation(); 1190 } 1191 if (mRefreshData != null) { 1192 synchronized (this) { 1193 final int count = mRefreshData.size(); 1194 for (int i = 0; i < count; i++) { 1195 final RefreshData rd = mRefreshData.get(i); 1196 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true); 1197 rd.recycle(); 1198 } 1199 mRefreshData.clear(); 1200 } 1201 } 1202 mAttached = true; 1203 } 1204 1205 @Override 1206 protected void onDetachedFromWindow() { 1207 if (mIndeterminate) { 1208 stopAnimation(); 1209 } 1210 if (mRefreshProgressRunnable != null) { 1211 removeCallbacks(mRefreshProgressRunnable); 1212 } 1213 if (mRefreshProgressRunnable != null && mRefreshIsPosted) { 1214 removeCallbacks(mRefreshProgressRunnable); 1215 } 1216 if (mAccessibilityEventSender != null) { 1217 removeCallbacks(mAccessibilityEventSender); 1218 } 1219 // This should come after stopAnimation(), otherwise an invalidate message remains in the 1220 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 1221 super.onDetachedFromWindow(); 1222 mAttached = false; 1223 } 1224 1225 @Override 1226 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1227 super.onInitializeAccessibilityEvent(event); 1228 event.setClassName(ProgressBar.class.getName()); 1229 event.setItemCount(mMax); 1230 event.setCurrentItemIndex(mProgress); 1231 } 1232 1233 @Override 1234 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1235 super.onInitializeAccessibilityNodeInfo(info); 1236 info.setClassName(ProgressBar.class.getName()); 1237 } 1238 1239 /** 1240 * Schedule a command for sending an accessibility event. 1241 * </br> 1242 * Note: A command is used to ensure that accessibility events 1243 * are sent at most one in a given time frame to save 1244 * system resources while the progress changes quickly. 1245 */ 1246 private void scheduleAccessibilityEventSender() { 1247 if (mAccessibilityEventSender == null) { 1248 mAccessibilityEventSender = new AccessibilityEventSender(); 1249 } else { 1250 removeCallbacks(mAccessibilityEventSender); 1251 } 1252 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 1253 } 1254 1255 /** 1256 * Command for sending an accessibility event. 1257 */ 1258 private class AccessibilityEventSender implements Runnable { 1259 public void run() { 1260 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1261 } 1262 } 1263} 1264