ProgressBar.java revision ef72a198afc4ded0b75534a97142fae2e91b46f5
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 android.annotation.Nullable; 20import android.graphics.PorterDuff; 21import com.android.internal.R; 22 23import android.content.Context; 24import android.content.res.ColorStateList; 25import android.content.res.TypedArray; 26import android.graphics.Bitmap; 27import android.graphics.BitmapShader; 28import android.graphics.Canvas; 29import android.graphics.PorterDuff.Mode; 30import android.graphics.Rect; 31import android.graphics.Shader; 32import android.graphics.drawable.Animatable; 33import android.graphics.drawable.AnimationDrawable; 34import android.graphics.drawable.BitmapDrawable; 35import android.graphics.drawable.ClipDrawable; 36import android.graphics.drawable.Drawable; 37import android.graphics.drawable.LayerDrawable; 38import android.graphics.drawable.ShapeDrawable; 39import android.graphics.drawable.StateListDrawable; 40import android.graphics.drawable.shapes.RoundRectShape; 41import android.graphics.drawable.shapes.Shape; 42import android.os.Parcel; 43import android.os.Parcelable; 44import android.util.AttributeSet; 45import android.util.Pools.SynchronizedPool; 46import android.view.Gravity; 47import android.view.RemotableViewMethod; 48import android.view.View; 49import android.view.ViewDebug; 50import android.view.accessibility.AccessibilityEvent; 51import android.view.accessibility.AccessibilityManager; 52import android.view.accessibility.AccessibilityNodeInfo; 53import android.view.animation.AlphaAnimation; 54import android.view.animation.Animation; 55import android.view.animation.AnimationUtils; 56import android.view.animation.Interpolator; 57import android.view.animation.LinearInterpolator; 58import android.view.animation.Transformation; 59import android.widget.RemoteViews.RemoteView; 60 61import java.util.ArrayList; 62 63 64/** 65 * <p> 66 * Visual indicator of progress in some operation. Displays a bar to the user 67 * representing how far the operation has progressed; the application can 68 * change the amount of progress (modifying the length of the bar) as it moves 69 * forward. There is also a secondary progress displayable on a progress bar 70 * which is useful for displaying intermediate progress, such as the buffer 71 * level during a streaming playback progress bar. 72 * </p> 73 * 74 * <p> 75 * A progress bar can also be made indeterminate. In indeterminate mode, the 76 * progress bar shows a cyclic animation without an indication of progress. This mode is used by 77 * applications when the length of the task is unknown. The indeterminate progress bar can be either 78 * a spinning wheel or a horizontal bar. 79 * </p> 80 * 81 * <p>The following code example shows how a progress bar can be used from 82 * a worker thread to update the user interface to notify the user of progress: 83 * </p> 84 * 85 * <pre> 86 * public class MyActivity extends Activity { 87 * private static final int PROGRESS = 0x1; 88 * 89 * private ProgressBar mProgress; 90 * private int mProgressStatus = 0; 91 * 92 * private Handler mHandler = new Handler(); 93 * 94 * protected void onCreate(Bundle icicle) { 95 * super.onCreate(icicle); 96 * 97 * setContentView(R.layout.progressbar_activity); 98 * 99 * mProgress = (ProgressBar) findViewById(R.id.progress_bar); 100 * 101 * // Start lengthy operation in a background thread 102 * new Thread(new Runnable() { 103 * public void run() { 104 * while (mProgressStatus < 100) { 105 * mProgressStatus = doWork(); 106 * 107 * // Update the progress bar 108 * mHandler.post(new Runnable() { 109 * public void run() { 110 * mProgress.setProgress(mProgressStatus); 111 * } 112 * }); 113 * } 114 * } 115 * }).start(); 116 * } 117 * }</pre> 118 * 119 * <p>To add a progress bar to a layout file, you can use the {@code <ProgressBar>} element. 120 * By default, the progress bar is a spinning wheel (an indeterminate indicator). To change to a 121 * horizontal progress bar, apply the {@link android.R.style#Widget_ProgressBar_Horizontal 122 * Widget.ProgressBar.Horizontal} style, like so:</p> 123 * 124 * <pre> 125 * <ProgressBar 126 * style="@android:style/Widget.ProgressBar.Horizontal" 127 * ... /></pre> 128 * 129 * <p>If you will use the progress bar to show real progress, you must use the horizontal bar. You 130 * can then increment the progress with {@link #incrementProgressBy incrementProgressBy()} or 131 * {@link #setProgress setProgress()}. By default, the progress bar is full when it reaches 100. If 132 * necessary, you can adjust the maximum value (the value for a full bar) using the {@link 133 * android.R.styleable#ProgressBar_max android:max} attribute. Other attributes available are listed 134 * below.</p> 135 * 136 * <p>Another common style to apply to the progress bar is {@link 137 * android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}, which shows a smaller 138 * version of the spinning wheel—useful when waiting for content to load. 139 * For example, you can insert this kind of progress bar into your default layout for 140 * a view that will be populated by some content fetched from the Internet—the spinning wheel 141 * appears immediately and when your application receives the content, it replaces the progress bar 142 * with the loaded content. For example:</p> 143 * 144 * <pre> 145 * <LinearLayout 146 * android:orientation="horizontal" 147 * ... > 148 * <ProgressBar 149 * android:layout_width="wrap_content" 150 * android:layout_height="wrap_content" 151 * style="@android:style/Widget.ProgressBar.Small" 152 * android:layout_marginRight="5dp" /> 153 * <TextView 154 * android:layout_width="wrap_content" 155 * android:layout_height="wrap_content" 156 * android:text="@string/loading" /> 157 * </LinearLayout></pre> 158 * 159 * <p>Other progress bar styles provided by the system include:</p> 160 * <ul> 161 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li> 162 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li> 163 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li> 164 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li> 165 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse 166 * Widget.ProgressBar.Small.Inverse}</li> 167 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse 168 * Widget.ProgressBar.Large.Inverse}</li> 169 * </ul> 170 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary 171 * if your application uses a light colored theme (a white background).</p> 172 * 173 * <p><strong>XML attributes</b></strong> 174 * <p> 175 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, 176 * {@link android.R.styleable#View View Attributes} 177 * </p> 178 * 179 * @attr ref android.R.styleable#ProgressBar_animationResolution 180 * @attr ref android.R.styleable#ProgressBar_indeterminate 181 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior 182 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable 183 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration 184 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly 185 * @attr ref android.R.styleable#ProgressBar_interpolator 186 * @attr ref android.R.styleable#ProgressBar_max 187 * @attr ref android.R.styleable#ProgressBar_maxHeight 188 * @attr ref android.R.styleable#ProgressBar_maxWidth 189 * @attr ref android.R.styleable#ProgressBar_minHeight 190 * @attr ref android.R.styleable#ProgressBar_minWidth 191 * @attr ref android.R.styleable#ProgressBar_mirrorForRtl 192 * @attr ref android.R.styleable#ProgressBar_progress 193 * @attr ref android.R.styleable#ProgressBar_progressDrawable 194 * @attr ref android.R.styleable#ProgressBar_secondaryProgress 195 */ 196@RemoteView 197public class ProgressBar extends View { 198 private static final int MAX_LEVEL = 10000; 199 private static final int TIMEOUT_SEND_ACCESSIBILITY_EVENT = 200; 200 201 int mMinWidth; 202 int mMaxWidth; 203 int mMinHeight; 204 int mMaxHeight; 205 206 private int mProgress; 207 private int mSecondaryProgress; 208 private int mMax; 209 210 private int mBehavior; 211 private int mDuration; 212 private boolean mIndeterminate; 213 private boolean mOnlyIndeterminate; 214 private Transformation mTransformation; 215 private AlphaAnimation mAnimation; 216 private boolean mHasAnimation; 217 218 private Drawable mIndeterminateDrawable; 219 private Drawable mProgressDrawable; 220 private Drawable mCurrentDrawable; 221 private ProgressTintInfo mProgressTintInfo; 222 223 Bitmap mSampleTile; 224 private boolean mNoInvalidate; 225 private Interpolator mInterpolator; 226 private RefreshProgressRunnable mRefreshProgressRunnable; 227 private long mUiThreadId; 228 private boolean mShouldStartAnimationDrawable; 229 230 private float mAnimationPosition; 231 232 private boolean mInDrawing; 233 private boolean mAttached; 234 private boolean mRefreshIsPosted; 235 236 boolean mMirrorForRtl = false; 237 238 private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>(); 239 240 private AccessibilityEventSender mAccessibilityEventSender; 241 242 /** 243 * Create a new progress bar with range 0...100 and initial progress of 0. 244 * @param context the application environment 245 */ 246 public ProgressBar(Context context) { 247 this(context, null); 248 } 249 250 public ProgressBar(Context context, AttributeSet attrs) { 251 this(context, attrs, com.android.internal.R.attr.progressBarStyle); 252 } 253 254 public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { 255 this(context, attrs, defStyleAttr, 0); 256 } 257 258 public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 259 super(context, attrs, defStyleAttr, defStyleRes); 260 261 mUiThreadId = Thread.currentThread().getId(); 262 initProgressBar(); 263 264 final TypedArray a = context.obtainStyledAttributes( 265 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); 266 267 mNoInvalidate = true; 268 269 final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); 270 if (progressDrawable != null) { 271 // Calling this method can set mMaxHeight, make sure the corresponding 272 // XML attribute for mMaxHeight is read after calling this method 273 setProgressDrawableTiled(progressDrawable); 274 } 275 276 277 mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration); 278 279 mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth); 280 mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth); 281 mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight); 282 mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight); 283 284 mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior); 285 286 final int resID = a.getResourceId( 287 com.android.internal.R.styleable.ProgressBar_interpolator, 288 android.R.anim.linear_interpolator); // default to linear interpolator 289 if (resID > 0) { 290 setInterpolator(context, resID); 291 } 292 293 setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); 294 295 setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress)); 296 297 setSecondaryProgress( 298 a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); 299 300 final Drawable indeterminateDrawable = a.getDrawable( 301 R.styleable.ProgressBar_indeterminateDrawable); 302 if (indeterminateDrawable != null) { 303 setIndeterminateDrawableTiled(indeterminateDrawable); 304 } 305 306 mOnlyIndeterminate = a.getBoolean( 307 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate); 308 309 mNoInvalidate = false; 310 311 setIndeterminate(mOnlyIndeterminate || a.getBoolean( 312 R.styleable.ProgressBar_indeterminate, mIndeterminate)); 313 314 mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); 315 316 if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) { 317 if (mProgressTintInfo == null) { 318 mProgressTintInfo = new ProgressTintInfo(); 319 } 320 mProgressTintInfo.mProgressTintMode = Drawable.parseTintMode(a.getInt( 321 R.styleable.ProgressBar_progressBackgroundTintMode, -1), null); 322 mProgressTintInfo.mHasProgressTintMode = true; 323 } 324 325 if (a.hasValue(R.styleable.ProgressBar_progressTint)) { 326 if (mProgressTintInfo == null) { 327 mProgressTintInfo = new ProgressTintInfo(); 328 } 329 mProgressTintInfo.mProgressTintList = a.getColorStateList( 330 R.styleable.ProgressBar_progressTint); 331 mProgressTintInfo.mHasProgressTint = true; 332 } 333 334 if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) { 335 if (mProgressTintInfo == null) { 336 mProgressTintInfo = new ProgressTintInfo(); 337 } 338 mProgressTintInfo.mProgressBackgroundTintMode = Drawable.parseTintMode(a.getInt( 339 R.styleable.ProgressBar_progressTintMode, -1), null); 340 mProgressTintInfo.mHasProgressBackgroundTintMode = true; 341 } 342 343 if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) { 344 if (mProgressTintInfo == null) { 345 mProgressTintInfo = new ProgressTintInfo(); 346 } 347 mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList( 348 R.styleable.ProgressBar_progressBackgroundTint); 349 mProgressTintInfo.mHasProgressBackgroundTint = true; 350 } 351 352 if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) { 353 if (mProgressTintInfo == null) { 354 mProgressTintInfo = new ProgressTintInfo(); 355 } 356 mProgressTintInfo.mSecondaryProgressTintMode = Drawable.parseTintMode( 357 a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null); 358 mProgressTintInfo.mHasSecondaryProgressTintMode = true; 359 } 360 361 if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) { 362 if (mProgressTintInfo == null) { 363 mProgressTintInfo = new ProgressTintInfo(); 364 } 365 mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList( 366 R.styleable.ProgressBar_secondaryProgressTint); 367 mProgressTintInfo.mHasSecondaryProgressTint = true; 368 } 369 370 if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { 371 if (mProgressTintInfo == null) { 372 mProgressTintInfo = new ProgressTintInfo(); 373 } 374 mProgressTintInfo.mIndeterminateTintMode = Drawable.parseTintMode(a.getInt( 375 R.styleable.ProgressBar_indeterminateTintMode, -1), null); 376 mProgressTintInfo.mHasIndeterminateTintMode = true; 377 } 378 379 if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { 380 if (mProgressTintInfo == null) { 381 mProgressTintInfo = new ProgressTintInfo(); 382 } 383 mProgressTintInfo.mIndeterminateTintList = a.getColorStateList( 384 R.styleable.ProgressBar_indeterminateTint); 385 mProgressTintInfo.mHasIndeterminateTint = true; 386 } 387 388 a.recycle(); 389 390 applyProgressTints(); 391 applyIndeterminateTint(); 392 393 // If not explicitly specified this view is important for accessibility. 394 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 395 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 396 } 397 } 398 399 /** 400 * Converts a drawable to a tiled version of itself. It will recursively 401 * traverse layer and state list drawables. 402 */ 403 private Drawable tileify(Drawable drawable, boolean clip) { 404 405 if (drawable instanceof LayerDrawable) { 406 LayerDrawable background = (LayerDrawable) drawable; 407 final int N = background.getNumberOfLayers(); 408 Drawable[] outDrawables = new Drawable[N]; 409 410 for (int i = 0; i < N; i++) { 411 int id = background.getId(i); 412 outDrawables[i] = tileify(background.getDrawable(i), 413 (id == R.id.progress || id == R.id.secondaryProgress)); 414 } 415 416 LayerDrawable newBg = new LayerDrawable(outDrawables); 417 418 for (int i = 0; i < N; i++) { 419 newBg.setId(i, background.getId(i)); 420 } 421 422 return newBg; 423 424 } else if (drawable instanceof StateListDrawable) { 425 StateListDrawable in = (StateListDrawable) drawable; 426 StateListDrawable out = new StateListDrawable(); 427 int numStates = in.getStateCount(); 428 for (int i = 0; i < numStates; i++) { 429 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); 430 } 431 return out; 432 433 } else if (drawable instanceof BitmapDrawable) { 434 final BitmapDrawable bitmap = (BitmapDrawable) drawable; 435 final Bitmap tileBitmap = bitmap.getBitmap(); 436 if (mSampleTile == null) { 437 mSampleTile = tileBitmap; 438 } 439 440 final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); 441 final BitmapShader bitmapShader = new BitmapShader(tileBitmap, 442 Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); 443 shapeDrawable.getPaint().setShader(bitmapShader); 444 445 // Ensure the tint and filter are propagated in the correct order. 446 shapeDrawable.setTintList(bitmap.getTint()); 447 shapeDrawable.setTintMode(bitmap.getTintMode()); 448 shapeDrawable.setColorFilter(bitmap.getColorFilter()); 449 450 return clip ? new ClipDrawable( 451 shapeDrawable, Gravity.LEFT, ClipDrawable.HORIZONTAL) : shapeDrawable; 452 } 453 454 return drawable; 455 } 456 457 Shape getDrawableShape() { 458 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; 459 return new RoundRectShape(roundedCorners, null, null); 460 } 461 462 /** 463 * Convert a AnimationDrawable for use as a barberpole animation. 464 * Each frame of the animation is wrapped in a ClipDrawable and 465 * given a tiling BitmapShader. 466 */ 467 private Drawable tileifyIndeterminate(Drawable drawable) { 468 if (drawable instanceof AnimationDrawable) { 469 AnimationDrawable background = (AnimationDrawable) drawable; 470 final int N = background.getNumberOfFrames(); 471 AnimationDrawable newBg = new AnimationDrawable(); 472 newBg.setOneShot(background.isOneShot()); 473 474 for (int i = 0; i < N; i++) { 475 Drawable frame = tileify(background.getFrame(i), true); 476 frame.setLevel(10000); 477 newBg.addFrame(frame, background.getDuration(i)); 478 } 479 newBg.setLevel(10000); 480 drawable = newBg; 481 } 482 return drawable; 483 } 484 485 /** 486 * <p> 487 * Initialize the progress bar's default values: 488 * </p> 489 * <ul> 490 * <li>progress = 0</li> 491 * <li>max = 100</li> 492 * <li>animation duration = 4000 ms</li> 493 * <li>indeterminate = false</li> 494 * <li>behavior = repeat</li> 495 * </ul> 496 */ 497 private void initProgressBar() { 498 mMax = 100; 499 mProgress = 0; 500 mSecondaryProgress = 0; 501 mIndeterminate = false; 502 mOnlyIndeterminate = false; 503 mDuration = 4000; 504 mBehavior = AlphaAnimation.RESTART; 505 mMinWidth = 24; 506 mMaxWidth = 48; 507 mMinHeight = 24; 508 mMaxHeight = 48; 509 } 510 511 /** 512 * <p>Indicate whether this progress bar is in indeterminate mode.</p> 513 * 514 * @return true if the progress bar is in indeterminate mode 515 */ 516 @ViewDebug.ExportedProperty(category = "progress") 517 public synchronized boolean isIndeterminate() { 518 return mIndeterminate; 519 } 520 521 /** 522 * <p>Change the indeterminate mode for this progress bar. In indeterminate 523 * mode, the progress is ignored and the progress bar shows an infinite 524 * animation instead.</p> 525 * 526 * If this progress bar's style only supports indeterminate mode (such as the circular 527 * progress bars), then this will be ignored. 528 * 529 * @param indeterminate true to enable the indeterminate mode 530 */ 531 @android.view.RemotableViewMethod 532 public synchronized void setIndeterminate(boolean indeterminate) { 533 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { 534 mIndeterminate = indeterminate; 535 536 if (indeterminate) { 537 // swap between indeterminate and regular backgrounds 538 mCurrentDrawable = mIndeterminateDrawable; 539 startAnimation(); 540 } else { 541 mCurrentDrawable = mProgressDrawable; 542 stopAnimation(); 543 } 544 } 545 } 546 547 /** 548 * <p>Get the drawable used to draw the progress bar in 549 * indeterminate mode.</p> 550 * 551 * @return a {@link android.graphics.drawable.Drawable} instance 552 * 553 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) 554 * @see #setIndeterminate(boolean) 555 */ 556 public Drawable getIndeterminateDrawable() { 557 return mIndeterminateDrawable; 558 } 559 560 /** 561 * Define the drawable used to draw the progress bar in indeterminate mode. 562 * 563 * @param d the new drawable 564 * @see #getIndeterminateDrawable() 565 * @see #setIndeterminate(boolean) 566 */ 567 public void setIndeterminateDrawable(Drawable d) { 568 if (mIndeterminateDrawable != d) { 569 if (mIndeterminateDrawable != null) { 570 mIndeterminateDrawable.setCallback(null); 571 unscheduleDrawable(mIndeterminateDrawable); 572 } 573 574 mIndeterminateDrawable = d; 575 576 if (d != null) { 577 d.setCallback(this); 578 d.setLayoutDirection(getLayoutDirection()); 579 if (d.isStateful()) { 580 d.setState(getDrawableState()); 581 } 582 applyIndeterminateTint(); 583 } 584 585 if (mIndeterminate) { 586 mCurrentDrawable = d; 587 postInvalidate(); 588 } 589 } 590 } 591 592 /** 593 * Applies a tint to the indeterminate drawable. Does not modify the 594 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 595 * <p> 596 * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will 597 * automatically mutate the drawable and apply the specified tint and 598 * tint mode using 599 * {@link Drawable#setTintList(ColorStateList)}. 600 * 601 * @param tint the tint to apply, may be {@code null} to clear tint 602 * 603 * @attr ref android.R.styleable#ProgressBar_indeterminateTint 604 * @see #getIndeterminateTintList() 605 * @see Drawable#setTintList(ColorStateList) 606 */ 607 @RemotableViewMethod 608 public void setIndeterminateTintList(@Nullable ColorStateList tint) { 609 if (mProgressTintInfo == null) { 610 mProgressTintInfo = new ProgressTintInfo(); 611 } 612 mProgressTintInfo.mIndeterminateTintList = tint; 613 mProgressTintInfo.mHasIndeterminateTint = true; 614 615 applyIndeterminateTint(); 616 } 617 618 /** 619 * @return the tint applied to the indeterminate drawable 620 * @attr ref android.R.styleable#ProgressBar_indeterminateTint 621 * @see #setIndeterminateTintList(ColorStateList) 622 */ 623 @Nullable 624 public ColorStateList getIndeterminateTintList() { 625 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null; 626 } 627 628 /** 629 * Specifies the blending mode used to apply the tint specified by 630 * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate 631 * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 632 * 633 * @param tintMode the blending mode used to apply the tint, may be 634 * {@code null} to clear tint 635 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 636 * @see #setIndeterminateTintList(ColorStateList) 637 * @see Drawable#setTintMode(PorterDuff.Mode) 638 */ 639 public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) { 640 if (mProgressTintInfo == null) { 641 mProgressTintInfo = new ProgressTintInfo(); 642 } 643 mProgressTintInfo.mIndeterminateTintMode = tintMode; 644 mProgressTintInfo.mHasIndeterminateTintMode = true; 645 646 applyIndeterminateTint(); 647 } 648 649 /** 650 * Returns the blending mode used to apply the tint to the indeterminate 651 * drawable, if specified. 652 * 653 * @return the blending mode used to apply the tint to the indeterminate 654 * drawable 655 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 656 * @see #setIndeterminateTintMode(PorterDuff.Mode) 657 */ 658 @Nullable 659 public PorterDuff.Mode getIndeterminateTintMode() { 660 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null; 661 } 662 663 private void applyIndeterminateTint() { 664 if (mIndeterminateDrawable != null && mProgressTintInfo != null) { 665 final ProgressTintInfo tintInfo = mProgressTintInfo; 666 if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) { 667 mIndeterminateDrawable = mIndeterminateDrawable.mutate(); 668 669 if (tintInfo.mHasIndeterminateTint) { 670 mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList); 671 } 672 673 if (tintInfo.mHasIndeterminateTintMode) { 674 mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode); 675 } 676 } 677 } 678 } 679 680 /** 681 * Define the tileable drawable used to draw the progress bar in 682 * indeterminate mode. 683 * <p> 684 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 685 * tiled copy will be generated for display as a progress bar. 686 * 687 * @param d the new drawable 688 * @see #getIndeterminateDrawable() 689 * @see #setIndeterminate(boolean) 690 */ 691 public void setIndeterminateDrawableTiled(Drawable d) { 692 if (d != null) { 693 d = tileifyIndeterminate(d); 694 } 695 696 setIndeterminateDrawable(d); 697 } 698 699 /** 700 * <p>Get the drawable used to draw the progress bar in 701 * progress mode.</p> 702 * 703 * @return a {@link android.graphics.drawable.Drawable} instance 704 * 705 * @see #setProgressDrawable(android.graphics.drawable.Drawable) 706 * @see #setIndeterminate(boolean) 707 */ 708 public Drawable getProgressDrawable() { 709 return mProgressDrawable; 710 } 711 712 /** 713 * Define the drawable used to draw the progress bar in progress mode. 714 * 715 * @param d the new drawable 716 * @see #getProgressDrawable() 717 * @see #setIndeterminate(boolean) 718 */ 719 public void setProgressDrawable(Drawable d) { 720 if (mProgressDrawable != d) { 721 if (mProgressDrawable != null) { 722 mProgressDrawable.setCallback(null); 723 unscheduleDrawable(mProgressDrawable); 724 } 725 726 mProgressDrawable = d; 727 728 if (d != null) { 729 d.setCallback(this); 730 d.setLayoutDirection(getLayoutDirection()); 731 if (d.isStateful()) { 732 d.setState(getDrawableState()); 733 } 734 735 // Make sure the ProgressBar is always tall enough 736 int drawableHeight = d.getMinimumHeight(); 737 if (mMaxHeight < drawableHeight) { 738 mMaxHeight = drawableHeight; 739 requestLayout(); 740 } 741 742 applyProgressTints(); 743 } 744 745 if (!mIndeterminate) { 746 mCurrentDrawable = d; 747 postInvalidate(); 748 } 749 750 updateDrawableBounds(getWidth(), getHeight()); 751 updateDrawableState(); 752 753 doRefreshProgress(R.id.progress, mProgress, false, false); 754 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 755 } 756 } 757 758 /** 759 * Applies the progress tints in order of increasing specificity. 760 */ 761 private void applyProgressTints() { 762 if (mProgressDrawable != null && mProgressTintInfo != null) { 763 applyPrimaryProgressTint(); 764 applyProgressBackgroundTint(); 765 applySecondaryProgressTint(); 766 } 767 } 768 769 /** 770 * Should only be called if we've already verified that mProgressDrawable 771 * and mProgressTintInfo are non-null. 772 */ 773 private void applyPrimaryProgressTint() { 774 if (mProgressTintInfo.mHasProgressTint 775 || mProgressTintInfo.mHasProgressTintMode) { 776 final Drawable target = getTintTarget(R.id.progress, true); 777 if (target != null) { 778 if (mProgressTintInfo.mHasProgressTint) { 779 target.setTintList(mProgressTintInfo.mProgressTintList); 780 } 781 if (mProgressTintInfo.mHasProgressTintMode) { 782 target.setTintMode(mProgressTintInfo.mProgressTintMode); 783 } 784 } 785 } 786 } 787 788 /** 789 * Should only be called if we've already verified that mProgressDrawable 790 * and mProgressTintInfo are non-null. 791 */ 792 private void applyProgressBackgroundTint() { 793 if (mProgressTintInfo.mHasProgressBackgroundTint 794 || mProgressTintInfo.mHasProgressBackgroundTintMode) { 795 final Drawable target = getTintTarget(R.id.background, false); 796 if (target != null) { 797 if (mProgressTintInfo.mHasProgressBackgroundTint) { 798 target.setTintList(mProgressTintInfo.mProgressBackgroundTintList); 799 } 800 if (mProgressTintInfo.mHasProgressBackgroundTintMode) { 801 target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode); 802 } 803 } 804 } 805 } 806 807 /** 808 * Should only be called if we've already verified that mProgressDrawable 809 * and mProgressTintInfo are non-null. 810 */ 811 private void applySecondaryProgressTint() { 812 if (mProgressTintInfo.mHasSecondaryProgressTint 813 || mProgressTintInfo.mHasSecondaryProgressTintMode) { 814 final Drawable target = getTintTarget(R.id.secondaryProgress, false); 815 if (target != null) { 816 if (mProgressTintInfo.mHasSecondaryProgressTint) { 817 target.setTintList(mProgressTintInfo.mSecondaryProgressTintList); 818 } 819 if (mProgressTintInfo.mHasSecondaryProgressTintMode) { 820 target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode); 821 } 822 } 823 } 824 } 825 826 /** 827 * Applies a tint to the progress indicator, if one exists, or to the 828 * entire progress drawable otherwise. Does not modify the current tint 829 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 830 * <p> 831 * The progress indicator should be specified as a layer with 832 * id {@link android.R.id#progress} in a {@link LayerDrawable} 833 * used as the progress drawable. 834 * <p> 835 * Subsequent calls to {@link #setProgressDrawable(Drawable)} will 836 * automatically mutate the drawable and apply the specified tint and 837 * tint mode using 838 * {@link Drawable#setTintList(ColorStateList)}. 839 * 840 * @param tint the tint to apply, may be {@code null} to clear tint 841 * 842 * @attr ref android.R.styleable#ProgressBar_progressTint 843 * @see #getProgressTintList() 844 * @see Drawable#setTintList(ColorStateList) 845 */ 846 @RemotableViewMethod 847 public void setProgressTintList(@Nullable ColorStateList tint) { 848 if (mProgressTintInfo == null) { 849 mProgressTintInfo = new ProgressTintInfo(); 850 } 851 mProgressTintInfo.mProgressTintList = tint; 852 mProgressTintInfo.mHasProgressTint = true; 853 854 if (mProgressDrawable != null) { 855 applyPrimaryProgressTint(); 856 } 857 } 858 859 /** 860 * Returns the tint applied to the progress drawable, if specified. 861 * 862 * @return the tint applied to the progress drawable 863 * @attr ref android.R.styleable#ProgressBar_progressTint 864 * @see #setProgressTintList(ColorStateList) 865 */ 866 @Nullable 867 public ColorStateList getProgressTintList() { 868 return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null; 869 } 870 871 /** 872 * Specifies the blending mode used to apply the tint specified by 873 * {@link #setProgressTintList(ColorStateList)}} to the progress 874 * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}. 875 * 876 * @param tintMode the blending mode used to apply the tint, may be 877 * {@code null} to clear tint 878 * @attr ref android.R.styleable#ProgressBar_progressTintMode 879 * @see #getProgressTintMode() 880 * @see Drawable#setTintMode(PorterDuff.Mode) 881 */ 882 public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 883 if (mProgressTintInfo == null) { 884 mProgressTintInfo = new ProgressTintInfo(); 885 } 886 mProgressTintInfo.mProgressTintMode = tintMode; 887 mProgressTintInfo.mHasProgressTintMode = true; 888 889 if (mProgressDrawable != null) { 890 applyPrimaryProgressTint(); 891 } 892 } 893 894 /** 895 * Returns the blending mode used to apply the tint to the progress 896 * drawable, if specified. 897 * 898 * @return the blending mode used to apply the tint to the progress 899 * drawable 900 * @attr ref android.R.styleable#ProgressBar_progressTintMode 901 * @see #setProgressTintMode(PorterDuff.Mode) 902 */ 903 @Nullable 904 public PorterDuff.Mode getProgressTintMode() { 905 return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null; 906 } 907 908 /** 909 * Applies a tint to the progress background, if one exists. Does not 910 * modify the current tint mode, which is 911 * {@link PorterDuff.Mode#SRC_ATOP} by default. 912 * <p> 913 * The progress background must be specified as a layer with 914 * id {@link android.R.id#background} in a {@link LayerDrawable} 915 * used as the progress drawable. 916 * <p> 917 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 918 * drawable contains a progress background will automatically mutate the 919 * drawable and apply the specified tint and tint mode using 920 * {@link Drawable#setTintList(ColorStateList)}. 921 * 922 * @param tint the tint to apply, may be {@code null} to clear tint 923 * 924 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 925 * @see #getProgressBackgroundTintList() 926 * @see Drawable#setTintList(ColorStateList) 927 */ 928 @RemotableViewMethod 929 public void setProgressBackgroundTintList(@Nullable ColorStateList tint) { 930 if (mProgressTintInfo == null) { 931 mProgressTintInfo = new ProgressTintInfo(); 932 } 933 mProgressTintInfo.mProgressBackgroundTintList = tint; 934 mProgressTintInfo.mHasProgressBackgroundTint = true; 935 936 if (mProgressDrawable != null) { 937 applyProgressBackgroundTint(); 938 } 939 } 940 941 /** 942 * Returns the tint applied to the progress background, if specified. 943 * 944 * @return the tint applied to the progress background 945 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 946 * @see #setProgressBackgroundTintList(ColorStateList) 947 */ 948 @Nullable 949 public ColorStateList getProgressBackgroundTintList() { 950 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null; 951 } 952 953 /** 954 * Specifies the blending mode used to apply the tint specified by 955 * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress 956 * background. The default mode is {@link PorterDuff.Mode#SRC_IN}. 957 * 958 * @param tintMode the blending mode used to apply the tint, may be 959 * {@code null} to clear tint 960 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 961 * @see #setProgressBackgroundTintList(ColorStateList) 962 * @see Drawable#setTintMode(PorterDuff.Mode) 963 */ 964 public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 965 if (mProgressTintInfo == null) { 966 mProgressTintInfo = new ProgressTintInfo(); 967 } 968 mProgressTintInfo.mProgressBackgroundTintMode = tintMode; 969 mProgressTintInfo.mHasProgressBackgroundTintMode = true; 970 971 if (mProgressDrawable != null) { 972 applyProgressBackgroundTint(); 973 } 974 } 975 976 /** 977 * @return the blending mode used to apply the tint to the progress 978 * background 979 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 980 * @see #setProgressBackgroundTintMode(PorterDuff.Mode) 981 */ 982 @Nullable 983 public PorterDuff.Mode getProgressBackgroundTintMode() { 984 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null; 985 } 986 987 /** 988 * Applies a tint to the secondary progress indicator, if one exists. 989 * Does not modify the current tint mode, which is 990 * {@link PorterDuff.Mode#SRC_ATOP} by default. 991 * <p> 992 * The secondary progress indicator must be specified as a layer with 993 * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} 994 * used as the progress drawable. 995 * <p> 996 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 997 * drawable contains a secondary progress indicator will automatically 998 * mutate the drawable and apply the specified tint and tint mode using 999 * {@link Drawable#setTintList(ColorStateList)}. 1000 * 1001 * @param tint the tint to apply, may be {@code null} to clear tint 1002 * 1003 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1004 * @see #getSecondaryProgressTintList() 1005 * @see Drawable#setTintList(ColorStateList) 1006 */ 1007 public void setSecondaryProgressTintList(@Nullable ColorStateList tint) { 1008 if (mProgressTintInfo == null) { 1009 mProgressTintInfo = new ProgressTintInfo(); 1010 } 1011 mProgressTintInfo.mSecondaryProgressTintList = tint; 1012 mProgressTintInfo.mHasSecondaryProgressTint = true; 1013 1014 if (mProgressDrawable != null) { 1015 applySecondaryProgressTint(); 1016 } 1017 } 1018 1019 /** 1020 * Returns the tint applied to the secondary progress drawable, if 1021 * specified. 1022 * 1023 * @return the tint applied to the secondary progress drawable 1024 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1025 * @see #setSecondaryProgressTintList(ColorStateList) 1026 */ 1027 @Nullable 1028 public ColorStateList getSecondaryProgressTintList() { 1029 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null; 1030 } 1031 1032 /** 1033 * Specifies the blending mode used to apply the tint specified by 1034 * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary 1035 * progress indicator. The default mode is 1036 * {@link PorterDuff.Mode#SRC_ATOP}. 1037 * 1038 * @param tintMode the blending mode used to apply the tint, may be 1039 * {@code null} to clear tint 1040 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1041 * @see #setSecondaryProgressTintList(ColorStateList) 1042 * @see Drawable#setTintMode(PorterDuff.Mode) 1043 */ 1044 public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 1045 if (mProgressTintInfo == null) { 1046 mProgressTintInfo = new ProgressTintInfo(); 1047 } 1048 mProgressTintInfo.mSecondaryProgressTintMode = tintMode; 1049 mProgressTintInfo.mHasSecondaryProgressTintMode = true; 1050 1051 if (mProgressDrawable != null) { 1052 applySecondaryProgressTint(); 1053 } 1054 } 1055 1056 /** 1057 * Returns the blending mode used to apply the tint to the secondary 1058 * progress drawable, if specified. 1059 * 1060 * @return the blending mode used to apply the tint to the secondary 1061 * progress drawable 1062 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1063 * @see #setSecondaryProgressTintMode(PorterDuff.Mode) 1064 */ 1065 @Nullable 1066 public PorterDuff.Mode getSecondaryProgressTintMode() { 1067 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null; 1068 } 1069 1070 /** 1071 * Returns the drawable to which a tint or tint mode should be applied. 1072 * 1073 * @param layerId id of the layer to modify 1074 * @param shouldFallback whether the base drawable should be returned 1075 * if the id does not exist 1076 * @return the drawable to modify 1077 */ 1078 @Nullable 1079 private Drawable getTintTarget(int layerId, boolean shouldFallback) { 1080 Drawable layer = null; 1081 1082 final Drawable d = mProgressDrawable; 1083 if (d != null) { 1084 mProgressDrawable = d.mutate(); 1085 1086 if (d instanceof LayerDrawable) { 1087 layer = ((LayerDrawable) d).findDrawableByLayerId(layerId); 1088 } 1089 1090 if (shouldFallback && layer == null) { 1091 layer = d; 1092 } 1093 } 1094 1095 return layer; 1096 } 1097 1098 /** 1099 * Define the tileable drawable used to draw the progress bar in 1100 * progress mode. 1101 * <p> 1102 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 1103 * tiled copy will be generated for display as a progress bar. 1104 * 1105 * @param d the new drawable 1106 * @see #getProgressDrawable() 1107 * @see #setIndeterminate(boolean) 1108 */ 1109 public void setProgressDrawableTiled(Drawable d) { 1110 if (d != null) { 1111 d = tileify(d, false); 1112 } 1113 1114 setProgressDrawable(d); 1115 } 1116 1117 /** 1118 * @return The drawable currently used to draw the progress bar 1119 */ 1120 Drawable getCurrentDrawable() { 1121 return mCurrentDrawable; 1122 } 1123 1124 @Override 1125 protected boolean verifyDrawable(Drawable who) { 1126 return who == mProgressDrawable || who == mIndeterminateDrawable 1127 || super.verifyDrawable(who); 1128 } 1129 1130 @Override 1131 public void jumpDrawablesToCurrentState() { 1132 super.jumpDrawablesToCurrentState(); 1133 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 1134 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 1135 } 1136 1137 /** 1138 * @hide 1139 */ 1140 @Override 1141 public void onResolveDrawables(int layoutDirection) { 1142 final Drawable d = mCurrentDrawable; 1143 if (d != null) { 1144 d.setLayoutDirection(layoutDirection); 1145 } 1146 if (mIndeterminateDrawable != null) { 1147 mIndeterminateDrawable.setLayoutDirection(layoutDirection); 1148 } 1149 if (mProgressDrawable != null) { 1150 mProgressDrawable.setLayoutDirection(layoutDirection); 1151 } 1152 } 1153 1154 @Override 1155 public void postInvalidate() { 1156 if (!mNoInvalidate) { 1157 super.postInvalidate(); 1158 } 1159 } 1160 1161 private class RefreshProgressRunnable implements Runnable { 1162 public void run() { 1163 synchronized (ProgressBar.this) { 1164 final int count = mRefreshData.size(); 1165 for (int i = 0; i < count; i++) { 1166 final RefreshData rd = mRefreshData.get(i); 1167 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); 1168 rd.recycle(); 1169 } 1170 mRefreshData.clear(); 1171 mRefreshIsPosted = false; 1172 } 1173 } 1174 } 1175 1176 private static class RefreshData { 1177 private static final int POOL_MAX = 24; 1178 private static final SynchronizedPool<RefreshData> sPool = 1179 new SynchronizedPool<RefreshData>(POOL_MAX); 1180 1181 public int id; 1182 public float progress; 1183 public boolean fromUser; 1184 public boolean animate; 1185 1186 public static RefreshData obtain(int id, float progress, boolean fromUser, 1187 boolean animate) { 1188 RefreshData rd = sPool.acquire(); 1189 if (rd == null) { 1190 rd = new RefreshData(); 1191 } 1192 rd.id = id; 1193 rd.progress = progress; 1194 rd.fromUser = fromUser; 1195 rd.animate = animate; 1196 return rd; 1197 } 1198 1199 public void recycle() { 1200 sPool.release(this); 1201 } 1202 } 1203 1204 private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) { 1205 Drawable layer = null; 1206 1207 // We expect a layer drawable, so try to find the target ID. 1208 final Drawable d = mCurrentDrawable; 1209 if (d instanceof LayerDrawable) { 1210 layer = ((LayerDrawable) d).findDrawableByLayerId(id); 1211 } 1212 1213 if (fallback && layer == null) { 1214 layer = d; 1215 } 1216 1217 layer.mutate(); 1218 layer.setTintList(tint); 1219 layer.setTintMode(tintMode); 1220 } 1221 1222 private float getScale(float progress) { 1223 return mMax > 0 ? progress / (float) mMax : 0; 1224 } 1225 1226 private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, 1227 boolean callBackToApp) { 1228 doRefreshProgress(id, progress, fromUser, callBackToApp, false); 1229 } 1230 1231 private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, 1232 boolean callBackToApp, boolean animate) { 1233 float scale = getScale(progress); 1234 1235 final Drawable d = mCurrentDrawable; 1236 if (d != null) { 1237 Drawable progressDrawable = null; 1238 1239 if (d instanceof LayerDrawable) { 1240 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); 1241 if (progressDrawable != null && canResolveLayoutDirection()) { 1242 progressDrawable.setLayoutDirection(getLayoutDirection()); 1243 } 1244 } 1245 1246 final int level = (int) (scale * MAX_LEVEL); 1247 (progressDrawable != null ? progressDrawable : d).setLevel(level); 1248 } else { 1249 invalidate(); 1250 } 1251 1252 if (id == R.id.progress) { 1253 if (animate) { 1254 onAnimatePosition(scale, fromUser); 1255 } else if (callBackToApp) { 1256 onProgressRefresh(scale, fromUser); 1257 } 1258 } 1259 } 1260 1261 /** 1262 * Called when a ProgressBar is animating its position. 1263 * 1264 * @param scale Current position/progress between 0 and 1. 1265 * @param fromUser True if the progress change was initiated by the user. 1266 */ 1267 void onAnimatePosition(float scale, boolean fromUser) { 1268 } 1269 1270 /** 1271 * Sets the progress value without going through the entire refresh process. 1272 * 1273 * @see #setProgress(int, boolean) 1274 * @param progress The new progress, between 0 and {@link #getMax()} 1275 */ 1276 void setProgressValueOnly(int progress) { 1277 mProgress = progress; 1278 onProgressRefresh(getScale(progress), true); 1279 } 1280 1281 void setAnimationPosition(float position) { 1282 mAnimationPosition = position; 1283 refreshProgress(R.id.progress, position, true, true); 1284 } 1285 1286 float getAnimationPosition() { 1287 return mAnimationPosition; 1288 } 1289 1290 void onProgressRefresh(float scale, boolean fromUser) { 1291 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 1292 scheduleAccessibilityEventSender(); 1293 } 1294 } 1295 1296 private synchronized void refreshProgress(int id, float progress, boolean fromUser) { 1297 refreshProgress(id, progress, fromUser, false); 1298 } 1299 1300 private synchronized void refreshProgress(int id, float progress, boolean fromUser, 1301 boolean animate) { 1302 if (mUiThreadId == Thread.currentThread().getId()) { 1303 doRefreshProgress(id, progress, fromUser, true, animate); 1304 } else { 1305 if (mRefreshProgressRunnable == null) { 1306 mRefreshProgressRunnable = new RefreshProgressRunnable(); 1307 } 1308 1309 final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); 1310 mRefreshData.add(rd); 1311 if (mAttached && !mRefreshIsPosted) { 1312 post(mRefreshProgressRunnable); 1313 mRefreshIsPosted = true; 1314 } 1315 } 1316 } 1317 1318 /** 1319 * <p>Set the current progress to the specified value. Does not do anything 1320 * if the progress bar is in indeterminate mode.</p> 1321 * 1322 * @param progress the new progress, between 0 and {@link #getMax()} 1323 * 1324 * @see #setIndeterminate(boolean) 1325 * @see #isIndeterminate() 1326 * @see #getProgress() 1327 * @see #incrementProgressBy(int) 1328 */ 1329 @android.view.RemotableViewMethod 1330 public synchronized void setProgress(int progress) { 1331 setProgress(progress, false); 1332 } 1333 1334 @android.view.RemotableViewMethod 1335 synchronized void setProgress(int progress, boolean fromUser) { 1336 if (mIndeterminate) { 1337 return; 1338 } 1339 1340 if (progress < 0) { 1341 progress = 0; 1342 } 1343 1344 if (progress > mMax) { 1345 progress = mMax; 1346 } 1347 1348 if (progress != mProgress) { 1349 mProgress = progress; 1350 refreshProgress(R.id.progress, mProgress, fromUser); 1351 } 1352 } 1353 1354 /** 1355 * <p> 1356 * Set the current secondary progress to the specified value. Does not do 1357 * anything if the progress bar is in indeterminate mode. 1358 * </p> 1359 * 1360 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} 1361 * @see #setIndeterminate(boolean) 1362 * @see #isIndeterminate() 1363 * @see #getSecondaryProgress() 1364 * @see #incrementSecondaryProgressBy(int) 1365 */ 1366 @android.view.RemotableViewMethod 1367 public synchronized void setSecondaryProgress(int secondaryProgress) { 1368 if (mIndeterminate) { 1369 return; 1370 } 1371 1372 if (secondaryProgress < 0) { 1373 secondaryProgress = 0; 1374 } 1375 1376 if (secondaryProgress > mMax) { 1377 secondaryProgress = mMax; 1378 } 1379 1380 if (secondaryProgress != mSecondaryProgress) { 1381 mSecondaryProgress = secondaryProgress; 1382 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false); 1383 } 1384 } 1385 1386 /** 1387 * <p>Get the progress bar's current level of progress. Return 0 when the 1388 * progress bar is in indeterminate mode.</p> 1389 * 1390 * @return the current progress, between 0 and {@link #getMax()} 1391 * 1392 * @see #setIndeterminate(boolean) 1393 * @see #isIndeterminate() 1394 * @see #setProgress(int) 1395 * @see #setMax(int) 1396 * @see #getMax() 1397 */ 1398 @ViewDebug.ExportedProperty(category = "progress") 1399 public synchronized int getProgress() { 1400 return mIndeterminate ? 0 : mProgress; 1401 } 1402 1403 /** 1404 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 1405 * progress bar is in indeterminate mode.</p> 1406 * 1407 * @return the current secondary progress, between 0 and {@link #getMax()} 1408 * 1409 * @see #setIndeterminate(boolean) 1410 * @see #isIndeterminate() 1411 * @see #setSecondaryProgress(int) 1412 * @see #setMax(int) 1413 * @see #getMax() 1414 */ 1415 @ViewDebug.ExportedProperty(category = "progress") 1416 public synchronized int getSecondaryProgress() { 1417 return mIndeterminate ? 0 : mSecondaryProgress; 1418 } 1419 1420 /** 1421 * <p>Return the upper limit of this progress bar's range.</p> 1422 * 1423 * @return a positive integer 1424 * 1425 * @see #setMax(int) 1426 * @see #getProgress() 1427 * @see #getSecondaryProgress() 1428 */ 1429 @ViewDebug.ExportedProperty(category = "progress") 1430 public synchronized int getMax() { 1431 return mMax; 1432 } 1433 1434 /** 1435 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p> 1436 * 1437 * @param max the upper range of this progress bar 1438 * 1439 * @see #getMax() 1440 * @see #setProgress(int) 1441 * @see #setSecondaryProgress(int) 1442 */ 1443 @android.view.RemotableViewMethod 1444 public synchronized void setMax(int max) { 1445 if (max < 0) { 1446 max = 0; 1447 } 1448 if (max != mMax) { 1449 mMax = max; 1450 postInvalidate(); 1451 1452 if (mProgress > max) { 1453 mProgress = max; 1454 } 1455 refreshProgress(R.id.progress, mProgress, false); 1456 } 1457 } 1458 1459 /** 1460 * <p>Increase the progress bar's progress by the specified amount.</p> 1461 * 1462 * @param diff the amount by which the progress must be increased 1463 * 1464 * @see #setProgress(int) 1465 */ 1466 public synchronized final void incrementProgressBy(int diff) { 1467 setProgress(mProgress + diff); 1468 } 1469 1470 /** 1471 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 1472 * 1473 * @param diff the amount by which the secondary progress must be increased 1474 * 1475 * @see #setSecondaryProgress(int) 1476 */ 1477 public synchronized final void incrementSecondaryProgressBy(int diff) { 1478 setSecondaryProgress(mSecondaryProgress + diff); 1479 } 1480 1481 /** 1482 * <p>Start the indeterminate progress animation.</p> 1483 */ 1484 void startAnimation() { 1485 if (getVisibility() != VISIBLE) { 1486 return; 1487 } 1488 1489 if (mIndeterminateDrawable instanceof Animatable) { 1490 mShouldStartAnimationDrawable = true; 1491 mHasAnimation = false; 1492 } else { 1493 mHasAnimation = true; 1494 1495 if (mInterpolator == null) { 1496 mInterpolator = new LinearInterpolator(); 1497 } 1498 1499 if (mTransformation == null) { 1500 mTransformation = new Transformation(); 1501 } else { 1502 mTransformation.clear(); 1503 } 1504 1505 if (mAnimation == null) { 1506 mAnimation = new AlphaAnimation(0.0f, 1.0f); 1507 } else { 1508 mAnimation.reset(); 1509 } 1510 1511 mAnimation.setRepeatMode(mBehavior); 1512 mAnimation.setRepeatCount(Animation.INFINITE); 1513 mAnimation.setDuration(mDuration); 1514 mAnimation.setInterpolator(mInterpolator); 1515 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 1516 } 1517 postInvalidate(); 1518 } 1519 1520 /** 1521 * <p>Stop the indeterminate progress animation.</p> 1522 */ 1523 void stopAnimation() { 1524 mHasAnimation = false; 1525 if (mIndeterminateDrawable instanceof Animatable) { 1526 ((Animatable) mIndeterminateDrawable).stop(); 1527 mShouldStartAnimationDrawable = false; 1528 } 1529 postInvalidate(); 1530 } 1531 1532 /** 1533 * Sets the acceleration curve for the indeterminate animation. 1534 * The interpolator is loaded as a resource from the specified context. 1535 * 1536 * @param context The application environment 1537 * @param resID The resource identifier of the interpolator to load 1538 */ 1539 public void setInterpolator(Context context, int resID) { 1540 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 1541 } 1542 1543 /** 1544 * Sets the acceleration curve for the indeterminate animation. 1545 * Defaults to a linear interpolation. 1546 * 1547 * @param interpolator The interpolator which defines the acceleration curve 1548 */ 1549 public void setInterpolator(Interpolator interpolator) { 1550 mInterpolator = interpolator; 1551 } 1552 1553 /** 1554 * Gets the acceleration curve type for the indeterminate animation. 1555 * 1556 * @return the {@link Interpolator} associated to this animation 1557 */ 1558 public Interpolator getInterpolator() { 1559 return mInterpolator; 1560 } 1561 1562 @Override 1563 @RemotableViewMethod 1564 public void setVisibility(int v) { 1565 if (getVisibility() != v) { 1566 super.setVisibility(v); 1567 1568 if (mIndeterminate) { 1569 // let's be nice with the UI thread 1570 if (v == GONE || v == INVISIBLE) { 1571 stopAnimation(); 1572 } else { 1573 startAnimation(); 1574 } 1575 } 1576 } 1577 } 1578 1579 @Override 1580 protected void onVisibilityChanged(View changedView, int visibility) { 1581 super.onVisibilityChanged(changedView, visibility); 1582 1583 if (mIndeterminate) { 1584 // let's be nice with the UI thread 1585 if (visibility == GONE || visibility == INVISIBLE) { 1586 stopAnimation(); 1587 } else { 1588 startAnimation(); 1589 } 1590 } 1591 } 1592 1593 @Override 1594 public void invalidateDrawable(Drawable dr) { 1595 if (!mInDrawing) { 1596 if (verifyDrawable(dr)) { 1597 final Rect dirty = dr.getBounds(); 1598 final int scrollX = mScrollX + mPaddingLeft; 1599 final int scrollY = mScrollY + mPaddingTop; 1600 1601 invalidate(dirty.left + scrollX, dirty.top + scrollY, 1602 dirty.right + scrollX, dirty.bottom + scrollY); 1603 } else { 1604 super.invalidateDrawable(dr); 1605 } 1606 } 1607 } 1608 1609 @Override 1610 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1611 updateDrawableBounds(w, h); 1612 } 1613 1614 private void updateDrawableBounds(int w, int h) { 1615 // onDraw will translate the canvas so we draw starting at 0,0. 1616 // Subtract out padding for the purposes of the calculations below. 1617 w -= mPaddingRight + mPaddingLeft; 1618 h -= mPaddingTop + mPaddingBottom; 1619 1620 int right = w; 1621 int bottom = h; 1622 int top = 0; 1623 int left = 0; 1624 1625 if (mIndeterminateDrawable != null) { 1626 // Aspect ratio logic does not apply to AnimationDrawables 1627 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 1628 // Maintain aspect ratio. Certain kinds of animated drawables 1629 // get very confused otherwise. 1630 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 1631 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 1632 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 1633 final float boundAspect = (float) w / h; 1634 if (intrinsicAspect != boundAspect) { 1635 if (boundAspect > intrinsicAspect) { 1636 // New width is larger. Make it smaller to match height. 1637 final int width = (int) (h * intrinsicAspect); 1638 left = (w - width) / 2; 1639 right = left + width; 1640 } else { 1641 // New height is larger. Make it smaller to match width. 1642 final int height = (int) (w * (1 / intrinsicAspect)); 1643 top = (h - height) / 2; 1644 bottom = top + height; 1645 } 1646 } 1647 } 1648 if (isLayoutRtl() && mMirrorForRtl) { 1649 int tempLeft = left; 1650 left = w - right; 1651 right = w - tempLeft; 1652 } 1653 mIndeterminateDrawable.setBounds(left, top, right, bottom); 1654 } 1655 1656 if (mProgressDrawable != null) { 1657 mProgressDrawable.setBounds(0, 0, right, bottom); 1658 } 1659 } 1660 1661 @Override 1662 protected synchronized void onDraw(Canvas canvas) { 1663 super.onDraw(canvas); 1664 1665 drawTrack(canvas); 1666 } 1667 1668 /** 1669 * Draws the progress bar track. 1670 */ 1671 void drawTrack(Canvas canvas) { 1672 final Drawable d = mCurrentDrawable; 1673 if (d != null) { 1674 // Translate canvas so a indeterminate circular progress bar with padding 1675 // rotates properly in its animation 1676 final int saveCount = canvas.save(); 1677 1678 if(isLayoutRtl() && mMirrorForRtl) { 1679 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 1680 canvas.scale(-1.0f, 1.0f); 1681 } else { 1682 canvas.translate(mPaddingLeft, mPaddingTop); 1683 } 1684 1685 final long time = getDrawingTime(); 1686 if (mHasAnimation) { 1687 mAnimation.getTransformation(time, mTransformation); 1688 final float scale = mTransformation.getAlpha(); 1689 try { 1690 mInDrawing = true; 1691 d.setLevel((int) (scale * MAX_LEVEL)); 1692 } finally { 1693 mInDrawing = false; 1694 } 1695 postInvalidateOnAnimation(); 1696 } 1697 1698 d.draw(canvas); 1699 canvas.restoreToCount(saveCount); 1700 1701 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 1702 ((Animatable) d).start(); 1703 mShouldStartAnimationDrawable = false; 1704 } 1705 } 1706 } 1707 1708 @Override 1709 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1710 Drawable d = mCurrentDrawable; 1711 1712 int dw = 0; 1713 int dh = 0; 1714 if (d != null) { 1715 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 1716 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 1717 } 1718 updateDrawableState(); 1719 dw += mPaddingLeft + mPaddingRight; 1720 dh += mPaddingTop + mPaddingBottom; 1721 1722 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), 1723 resolveSizeAndState(dh, heightMeasureSpec, 0)); 1724 } 1725 1726 @Override 1727 protected void drawableStateChanged() { 1728 super.drawableStateChanged(); 1729 updateDrawableState(); 1730 } 1731 1732 private void updateDrawableState() { 1733 int[] state = getDrawableState(); 1734 1735 if (mProgressDrawable != null && mProgressDrawable.isStateful()) { 1736 mProgressDrawable.setState(state); 1737 } 1738 1739 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { 1740 mIndeterminateDrawable.setState(state); 1741 } 1742 } 1743 1744 @Override 1745 public void drawableHotspotChanged(float x, float y) { 1746 super.drawableHotspotChanged(x, y); 1747 1748 if (mProgressDrawable != null) { 1749 mProgressDrawable.setHotspot(x, y); 1750 } 1751 1752 if (mIndeterminateDrawable != null) { 1753 mIndeterminateDrawable.setHotspot(x, y); 1754 } 1755 } 1756 1757 static class SavedState extends BaseSavedState { 1758 int progress; 1759 int secondaryProgress; 1760 1761 /** 1762 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 1763 */ 1764 SavedState(Parcelable superState) { 1765 super(superState); 1766 } 1767 1768 /** 1769 * Constructor called from {@link #CREATOR} 1770 */ 1771 private SavedState(Parcel in) { 1772 super(in); 1773 progress = in.readInt(); 1774 secondaryProgress = in.readInt(); 1775 } 1776 1777 @Override 1778 public void writeToParcel(Parcel out, int flags) { 1779 super.writeToParcel(out, flags); 1780 out.writeInt(progress); 1781 out.writeInt(secondaryProgress); 1782 } 1783 1784 public static final Parcelable.Creator<SavedState> CREATOR 1785 = new Parcelable.Creator<SavedState>() { 1786 public SavedState createFromParcel(Parcel in) { 1787 return new SavedState(in); 1788 } 1789 1790 public SavedState[] newArray(int size) { 1791 return new SavedState[size]; 1792 } 1793 }; 1794 } 1795 1796 @Override 1797 public Parcelable onSaveInstanceState() { 1798 // Force our ancestor class to save its state 1799 Parcelable superState = super.onSaveInstanceState(); 1800 SavedState ss = new SavedState(superState); 1801 1802 ss.progress = mProgress; 1803 ss.secondaryProgress = mSecondaryProgress; 1804 1805 return ss; 1806 } 1807 1808 @Override 1809 public void onRestoreInstanceState(Parcelable state) { 1810 SavedState ss = (SavedState) state; 1811 super.onRestoreInstanceState(ss.getSuperState()); 1812 1813 setProgress(ss.progress); 1814 setSecondaryProgress(ss.secondaryProgress); 1815 } 1816 1817 @Override 1818 protected void onAttachedToWindow() { 1819 super.onAttachedToWindow(); 1820 if (mIndeterminate) { 1821 startAnimation(); 1822 } 1823 if (mRefreshData != null) { 1824 synchronized (this) { 1825 final int count = mRefreshData.size(); 1826 for (int i = 0; i < count; i++) { 1827 final RefreshData rd = mRefreshData.get(i); 1828 doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate); 1829 rd.recycle(); 1830 } 1831 mRefreshData.clear(); 1832 } 1833 } 1834 mAttached = true; 1835 } 1836 1837 @Override 1838 protected void onDetachedFromWindow() { 1839 if (mIndeterminate) { 1840 stopAnimation(); 1841 } 1842 if (mRefreshProgressRunnable != null) { 1843 removeCallbacks(mRefreshProgressRunnable); 1844 } 1845 if (mRefreshProgressRunnable != null && mRefreshIsPosted) { 1846 removeCallbacks(mRefreshProgressRunnable); 1847 } 1848 if (mAccessibilityEventSender != null) { 1849 removeCallbacks(mAccessibilityEventSender); 1850 } 1851 // This should come after stopAnimation(), otherwise an invalidate message remains in the 1852 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 1853 super.onDetachedFromWindow(); 1854 mAttached = false; 1855 } 1856 1857 @Override 1858 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1859 super.onInitializeAccessibilityEvent(event); 1860 event.setClassName(ProgressBar.class.getName()); 1861 event.setItemCount(mMax); 1862 event.setCurrentItemIndex(mProgress); 1863 } 1864 1865 @Override 1866 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1867 super.onInitializeAccessibilityNodeInfo(info); 1868 info.setClassName(ProgressBar.class.getName()); 1869 } 1870 1871 /** 1872 * Schedule a command for sending an accessibility event. 1873 * </br> 1874 * Note: A command is used to ensure that accessibility events 1875 * are sent at most one in a given time frame to save 1876 * system resources while the progress changes quickly. 1877 */ 1878 private void scheduleAccessibilityEventSender() { 1879 if (mAccessibilityEventSender == null) { 1880 mAccessibilityEventSender = new AccessibilityEventSender(); 1881 } else { 1882 removeCallbacks(mAccessibilityEventSender); 1883 } 1884 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 1885 } 1886 1887 /** 1888 * Command for sending an accessibility event. 1889 */ 1890 private class AccessibilityEventSender implements Runnable { 1891 public void run() { 1892 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1893 } 1894 } 1895 1896 private static class ProgressTintInfo { 1897 ColorStateList mIndeterminateTintList; 1898 PorterDuff.Mode mIndeterminateTintMode; 1899 boolean mHasIndeterminateTint; 1900 boolean mHasIndeterminateTintMode; 1901 1902 ColorStateList mProgressTintList; 1903 PorterDuff.Mode mProgressTintMode; 1904 boolean mHasProgressTint; 1905 boolean mHasProgressTintMode; 1906 1907 ColorStateList mProgressBackgroundTintList; 1908 PorterDuff.Mode mProgressBackgroundTintMode; 1909 boolean mHasProgressBackgroundTint; 1910 boolean mHasProgressBackgroundTintMode; 1911 1912 ColorStateList mSecondaryProgressTintList; 1913 PorterDuff.Mode mSecondaryProgressTintMode; 1914 boolean mHasSecondaryProgressTint; 1915 boolean mHasSecondaryProgressTintMode; 1916 } 1917} 1918