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