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 public void setIndeterminateTintList(@Nullable ColorStateList tint) { 608 if (mProgressTintInfo == null) { 609 mProgressTintInfo = new ProgressTintInfo(); 610 } 611 mProgressTintInfo.mIndeterminateTintList = tint; 612 mProgressTintInfo.mHasIndeterminateTint = true; 613 614 applyIndeterminateTint(); 615 } 616 617 /** 618 * @return the tint applied to the indeterminate drawable 619 * @attr ref android.R.styleable#ProgressBar_indeterminateTint 620 * @see #setIndeterminateTintList(ColorStateList) 621 */ 622 @Nullable 623 public ColorStateList getIndeterminateTintList() { 624 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null; 625 } 626 627 /** 628 * Specifies the blending mode used to apply the tint specified by 629 * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate 630 * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 631 * 632 * @param tintMode the blending mode used to apply the tint, may be 633 * {@code null} to clear tint 634 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 635 * @see #setIndeterminateTintList(ColorStateList) 636 * @see Drawable#setTintMode(PorterDuff.Mode) 637 */ 638 public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) { 639 if (mProgressTintInfo == null) { 640 mProgressTintInfo = new ProgressTintInfo(); 641 } 642 mProgressTintInfo.mIndeterminateTintMode = tintMode; 643 mProgressTintInfo.mHasIndeterminateTintMode = true; 644 645 applyIndeterminateTint(); 646 } 647 648 /** 649 * Returns the blending mode used to apply the tint to the indeterminate 650 * drawable, if specified. 651 * 652 * @return the blending mode used to apply the tint to the indeterminate 653 * drawable 654 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 655 * @see #setIndeterminateTintMode(PorterDuff.Mode) 656 */ 657 @Nullable 658 public PorterDuff.Mode getIndeterminateTintMode() { 659 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintMode : null; 660 } 661 662 private void applyIndeterminateTint() { 663 if (mIndeterminateDrawable != null && mProgressTintInfo != null) { 664 final ProgressTintInfo tintInfo = mProgressTintInfo; 665 if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) { 666 mIndeterminateDrawable = mIndeterminateDrawable.mutate(); 667 668 if (tintInfo.mHasIndeterminateTint) { 669 mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList); 670 } 671 672 if (tintInfo.mHasIndeterminateTintMode) { 673 mIndeterminateDrawable.setTintMode(tintInfo.mIndeterminateTintMode); 674 } 675 } 676 } 677 } 678 679 /** 680 * Define the tileable drawable used to draw the progress bar in 681 * indeterminate mode. 682 * <p> 683 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 684 * tiled copy will be generated for display as a progress bar. 685 * 686 * @param d the new drawable 687 * @see #getIndeterminateDrawable() 688 * @see #setIndeterminate(boolean) 689 */ 690 public void setIndeterminateDrawableTiled(Drawable d) { 691 if (d != null) { 692 d = tileifyIndeterminate(d); 693 } 694 695 setIndeterminateDrawable(d); 696 } 697 698 /** 699 * <p>Get the drawable used to draw the progress bar in 700 * progress mode.</p> 701 * 702 * @return a {@link android.graphics.drawable.Drawable} instance 703 * 704 * @see #setProgressDrawable(android.graphics.drawable.Drawable) 705 * @see #setIndeterminate(boolean) 706 */ 707 public Drawable getProgressDrawable() { 708 return mProgressDrawable; 709 } 710 711 /** 712 * Define the drawable used to draw the progress bar in progress mode. 713 * 714 * @param d the new drawable 715 * @see #getProgressDrawable() 716 * @see #setIndeterminate(boolean) 717 */ 718 public void setProgressDrawable(Drawable d) { 719 if (mProgressDrawable != d) { 720 if (mProgressDrawable != null) { 721 mProgressDrawable.setCallback(null); 722 unscheduleDrawable(mProgressDrawable); 723 } 724 725 mProgressDrawable = d; 726 727 if (d != null) { 728 d.setCallback(this); 729 d.setLayoutDirection(getLayoutDirection()); 730 if (d.isStateful()) { 731 d.setState(getDrawableState()); 732 } 733 734 // Make sure the ProgressBar is always tall enough 735 int drawableHeight = d.getMinimumHeight(); 736 if (mMaxHeight < drawableHeight) { 737 mMaxHeight = drawableHeight; 738 requestLayout(); 739 } 740 741 applyProgressTints(); 742 } 743 744 if (!mIndeterminate) { 745 mCurrentDrawable = d; 746 postInvalidate(); 747 } 748 749 updateDrawableBounds(getWidth(), getHeight()); 750 updateDrawableState(); 751 752 doRefreshProgress(R.id.progress, mProgress, false, false); 753 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 754 } 755 } 756 757 /** 758 * Applies the progress tints in order of increasing specificity. 759 */ 760 private void applyProgressTints() { 761 if (mProgressDrawable != null && mProgressTintInfo != null) { 762 applyPrimaryProgressTint(); 763 applyProgressBackgroundTint(); 764 applySecondaryProgressTint(); 765 } 766 } 767 768 /** 769 * Should only be called if we've already verified that mProgressDrawable 770 * and mProgressTintInfo are non-null. 771 */ 772 private void applyPrimaryProgressTint() { 773 if (mProgressTintInfo.mHasProgressTint 774 || mProgressTintInfo.mHasProgressTintMode) { 775 final Drawable target = getTintTarget(R.id.progress, true); 776 if (target != null) { 777 if (mProgressTintInfo.mHasProgressTint) { 778 target.setTintList(mProgressTintInfo.mProgressTintList); 779 } 780 if (mProgressTintInfo.mHasProgressTintMode) { 781 target.setTintMode(mProgressTintInfo.mProgressTintMode); 782 } 783 } 784 } 785 } 786 787 /** 788 * Should only be called if we've already verified that mProgressDrawable 789 * and mProgressTintInfo are non-null. 790 */ 791 private void applyProgressBackgroundTint() { 792 if (mProgressTintInfo.mHasProgressBackgroundTint 793 || mProgressTintInfo.mHasProgressBackgroundTintMode) { 794 final Drawable target = getTintTarget(R.id.background, false); 795 if (target != null) { 796 if (mProgressTintInfo.mHasProgressBackgroundTint) { 797 target.setTintList(mProgressTintInfo.mProgressBackgroundTintList); 798 } 799 if (mProgressTintInfo.mHasProgressBackgroundTintMode) { 800 target.setTintMode(mProgressTintInfo.mProgressBackgroundTintMode); 801 } 802 } 803 } 804 } 805 806 /** 807 * Should only be called if we've already verified that mProgressDrawable 808 * and mProgressTintInfo are non-null. 809 */ 810 private void applySecondaryProgressTint() { 811 if (mProgressTintInfo.mHasSecondaryProgressTint 812 || mProgressTintInfo.mHasSecondaryProgressTintMode) { 813 final Drawable target = getTintTarget(R.id.secondaryProgress, false); 814 if (target != null) { 815 if (mProgressTintInfo.mHasSecondaryProgressTint) { 816 target.setTintList(mProgressTintInfo.mSecondaryProgressTintList); 817 } 818 if (mProgressTintInfo.mHasSecondaryProgressTintMode) { 819 target.setTintMode(mProgressTintInfo.mSecondaryProgressTintMode); 820 } 821 } 822 } 823 } 824 825 /** 826 * Applies a tint to the progress indicator, if one exists, or to the 827 * entire progress drawable otherwise. Does not modify the current tint 828 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 829 * <p> 830 * The progress indicator should be specified as a layer with 831 * id {@link android.R.id#progress} in a {@link LayerDrawable} 832 * used as the progress drawable. 833 * <p> 834 * Subsequent calls to {@link #setProgressDrawable(Drawable)} will 835 * automatically mutate the drawable and apply the specified tint and 836 * tint mode using 837 * {@link Drawable#setTintList(ColorStateList)}. 838 * 839 * @param tint the tint to apply, may be {@code null} to clear tint 840 * 841 * @attr ref android.R.styleable#ProgressBar_progressTint 842 * @see #getProgressTintList() 843 * @see Drawable#setTintList(ColorStateList) 844 */ 845 public void setProgressTintList(@Nullable ColorStateList tint) { 846 if (mProgressTintInfo == null) { 847 mProgressTintInfo = new ProgressTintInfo(); 848 } 849 mProgressTintInfo.mProgressTintList = tint; 850 mProgressTintInfo.mHasProgressTint = true; 851 852 if (mProgressDrawable != null) { 853 applyPrimaryProgressTint(); 854 } 855 } 856 857 /** 858 * Returns the tint applied to the progress drawable, if specified. 859 * 860 * @return the tint applied to the progress drawable 861 * @attr ref android.R.styleable#ProgressBar_progressTint 862 * @see #setProgressTintList(ColorStateList) 863 */ 864 @Nullable 865 public ColorStateList getProgressTintList() { 866 return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null; 867 } 868 869 /** 870 * Specifies the blending mode used to apply the tint specified by 871 * {@link #setProgressTintList(ColorStateList)}} to the progress 872 * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}. 873 * 874 * @param tintMode the blending mode used to apply the tint, may be 875 * {@code null} to clear tint 876 * @attr ref android.R.styleable#ProgressBar_progressTintMode 877 * @see #getProgressTintMode() 878 * @see Drawable#setTintMode(PorterDuff.Mode) 879 */ 880 public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 881 if (mProgressTintInfo == null) { 882 mProgressTintInfo = new ProgressTintInfo(); 883 } 884 mProgressTintInfo.mProgressTintMode = tintMode; 885 mProgressTintInfo.mHasProgressTintMode = true; 886 887 if (mProgressDrawable != null) { 888 applyPrimaryProgressTint(); 889 } 890 } 891 892 /** 893 * Returns the blending mode used to apply the tint to the progress 894 * drawable, if specified. 895 * 896 * @return the blending mode used to apply the tint to the progress 897 * drawable 898 * @attr ref android.R.styleable#ProgressBar_progressTintMode 899 * @see #setProgressTintMode(PorterDuff.Mode) 900 */ 901 @Nullable 902 public PorterDuff.Mode getProgressTintMode() { 903 return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintMode : null; 904 } 905 906 /** 907 * Applies a tint to the progress background, if one exists. Does not 908 * modify the current tint mode, which is 909 * {@link PorterDuff.Mode#SRC_ATOP} by default. 910 * <p> 911 * The progress background must be specified as a layer with 912 * id {@link android.R.id#background} in a {@link LayerDrawable} 913 * used as the progress drawable. 914 * <p> 915 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 916 * drawable contains a progress background will automatically mutate the 917 * drawable and apply the specified tint and tint mode using 918 * {@link Drawable#setTintList(ColorStateList)}. 919 * 920 * @param tint the tint to apply, may be {@code null} to clear tint 921 * 922 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 923 * @see #getProgressBackgroundTintList() 924 * @see Drawable#setTintList(ColorStateList) 925 */ 926 public void setProgressBackgroundTintList(@Nullable ColorStateList tint) { 927 if (mProgressTintInfo == null) { 928 mProgressTintInfo = new ProgressTintInfo(); 929 } 930 mProgressTintInfo.mProgressBackgroundTintList = tint; 931 mProgressTintInfo.mHasProgressBackgroundTint = true; 932 933 if (mProgressDrawable != null) { 934 applyProgressBackgroundTint(); 935 } 936 } 937 938 /** 939 * Returns the tint applied to the progress background, if specified. 940 * 941 * @return the tint applied to the progress background 942 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 943 * @see #setProgressBackgroundTintList(ColorStateList) 944 */ 945 @Nullable 946 public ColorStateList getProgressBackgroundTintList() { 947 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null; 948 } 949 950 /** 951 * Specifies the blending mode used to apply the tint specified by 952 * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress 953 * background. The default mode is {@link PorterDuff.Mode#SRC_IN}. 954 * 955 * @param tintMode the blending mode used to apply the tint, may be 956 * {@code null} to clear tint 957 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 958 * @see #setProgressBackgroundTintList(ColorStateList) 959 * @see Drawable#setTintMode(PorterDuff.Mode) 960 */ 961 public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 962 if (mProgressTintInfo == null) { 963 mProgressTintInfo = new ProgressTintInfo(); 964 } 965 mProgressTintInfo.mProgressBackgroundTintMode = tintMode; 966 mProgressTintInfo.mHasProgressBackgroundTintMode = true; 967 968 if (mProgressDrawable != null) { 969 applyProgressBackgroundTint(); 970 } 971 } 972 973 /** 974 * @return the blending mode used to apply the tint to the progress 975 * background 976 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 977 * @see #setProgressBackgroundTintMode(PorterDuff.Mode) 978 */ 979 @Nullable 980 public PorterDuff.Mode getProgressBackgroundTintMode() { 981 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintMode : null; 982 } 983 984 /** 985 * Applies a tint to the secondary progress indicator, if one exists. 986 * Does not modify the current tint mode, which is 987 * {@link PorterDuff.Mode#SRC_ATOP} by default. 988 * <p> 989 * The secondary progress indicator must be specified as a layer with 990 * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} 991 * used as the progress drawable. 992 * <p> 993 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 994 * drawable contains a secondary progress indicator will automatically 995 * mutate the drawable and apply the specified tint and tint mode using 996 * {@link Drawable#setTintList(ColorStateList)}. 997 * 998 * @param tint the tint to apply, may be {@code null} to clear tint 999 * 1000 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1001 * @see #getSecondaryProgressTintList() 1002 * @see Drawable#setTintList(ColorStateList) 1003 */ 1004 public void setSecondaryProgressTintList(@Nullable ColorStateList tint) { 1005 if (mProgressTintInfo == null) { 1006 mProgressTintInfo = new ProgressTintInfo(); 1007 } 1008 mProgressTintInfo.mSecondaryProgressTintList = tint; 1009 mProgressTintInfo.mHasSecondaryProgressTint = true; 1010 1011 if (mProgressDrawable != null) { 1012 applySecondaryProgressTint(); 1013 } 1014 } 1015 1016 /** 1017 * Returns the tint applied to the secondary progress drawable, if 1018 * specified. 1019 * 1020 * @return the tint applied to the secondary progress drawable 1021 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1022 * @see #setSecondaryProgressTintList(ColorStateList) 1023 */ 1024 @Nullable 1025 public ColorStateList getSecondaryProgressTintList() { 1026 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null; 1027 } 1028 1029 /** 1030 * Specifies the blending mode used to apply the tint specified by 1031 * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary 1032 * progress indicator. The default mode is 1033 * {@link PorterDuff.Mode#SRC_ATOP}. 1034 * 1035 * @param tintMode the blending mode used to apply the tint, may be 1036 * {@code null} to clear tint 1037 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1038 * @see #setSecondaryProgressTintList(ColorStateList) 1039 * @see Drawable#setTintMode(PorterDuff.Mode) 1040 */ 1041 public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 1042 if (mProgressTintInfo == null) { 1043 mProgressTintInfo = new ProgressTintInfo(); 1044 } 1045 mProgressTintInfo.mSecondaryProgressTintMode = tintMode; 1046 mProgressTintInfo.mHasSecondaryProgressTintMode = true; 1047 1048 if (mProgressDrawable != null) { 1049 applySecondaryProgressTint(); 1050 } 1051 } 1052 1053 /** 1054 * Returns the blending mode used to apply the tint to the secondary 1055 * progress drawable, if specified. 1056 * 1057 * @return the blending mode used to apply the tint to the secondary 1058 * progress drawable 1059 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1060 * @see #setSecondaryProgressTintMode(PorterDuff.Mode) 1061 */ 1062 @Nullable 1063 public PorterDuff.Mode getSecondaryProgressTintMode() { 1064 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintMode : null; 1065 } 1066 1067 /** 1068 * Returns the drawable to which a tint or tint mode should be applied. 1069 * 1070 * @param layerId id of the layer to modify 1071 * @param shouldFallback whether the base drawable should be returned 1072 * if the id does not exist 1073 * @return the drawable to modify 1074 */ 1075 @Nullable 1076 private Drawable getTintTarget(int layerId, boolean shouldFallback) { 1077 Drawable layer = null; 1078 1079 final Drawable d = mProgressDrawable; 1080 if (d != null) { 1081 mProgressDrawable = d.mutate(); 1082 1083 if (d instanceof LayerDrawable) { 1084 layer = ((LayerDrawable) d).findDrawableByLayerId(layerId); 1085 } 1086 1087 if (shouldFallback && layer == null) { 1088 layer = d; 1089 } 1090 } 1091 1092 return layer; 1093 } 1094 1095 /** 1096 * Define the tileable drawable used to draw the progress bar in 1097 * progress mode. 1098 * <p> 1099 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 1100 * tiled copy will be generated for display as a progress bar. 1101 * 1102 * @param d the new drawable 1103 * @see #getProgressDrawable() 1104 * @see #setIndeterminate(boolean) 1105 */ 1106 public void setProgressDrawableTiled(Drawable d) { 1107 if (d != null) { 1108 d = tileify(d, false); 1109 } 1110 1111 setProgressDrawable(d); 1112 } 1113 1114 /** 1115 * @return The drawable currently used to draw the progress bar 1116 */ 1117 Drawable getCurrentDrawable() { 1118 return mCurrentDrawable; 1119 } 1120 1121 @Override 1122 protected boolean verifyDrawable(Drawable who) { 1123 return who == mProgressDrawable || who == mIndeterminateDrawable 1124 || super.verifyDrawable(who); 1125 } 1126 1127 @Override 1128 public void jumpDrawablesToCurrentState() { 1129 super.jumpDrawablesToCurrentState(); 1130 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 1131 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 1132 } 1133 1134 /** 1135 * @hide 1136 */ 1137 @Override 1138 public void onResolveDrawables(int layoutDirection) { 1139 final Drawable d = mCurrentDrawable; 1140 if (d != null) { 1141 d.setLayoutDirection(layoutDirection); 1142 } 1143 if (mIndeterminateDrawable != null) { 1144 mIndeterminateDrawable.setLayoutDirection(layoutDirection); 1145 } 1146 if (mProgressDrawable != null) { 1147 mProgressDrawable.setLayoutDirection(layoutDirection); 1148 } 1149 } 1150 1151 @Override 1152 public void postInvalidate() { 1153 if (!mNoInvalidate) { 1154 super.postInvalidate(); 1155 } 1156 } 1157 1158 private class RefreshProgressRunnable implements Runnable { 1159 public void run() { 1160 synchronized (ProgressBar.this) { 1161 final int count = mRefreshData.size(); 1162 for (int i = 0; i < count; i++) { 1163 final RefreshData rd = mRefreshData.get(i); 1164 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); 1165 rd.recycle(); 1166 } 1167 mRefreshData.clear(); 1168 mRefreshIsPosted = false; 1169 } 1170 } 1171 } 1172 1173 private static class RefreshData { 1174 private static final int POOL_MAX = 24; 1175 private static final SynchronizedPool<RefreshData> sPool = 1176 new SynchronizedPool<RefreshData>(POOL_MAX); 1177 1178 public int id; 1179 public float progress; 1180 public boolean fromUser; 1181 public boolean animate; 1182 1183 public static RefreshData obtain(int id, float progress, boolean fromUser, 1184 boolean animate) { 1185 RefreshData rd = sPool.acquire(); 1186 if (rd == null) { 1187 rd = new RefreshData(); 1188 } 1189 rd.id = id; 1190 rd.progress = progress; 1191 rd.fromUser = fromUser; 1192 rd.animate = animate; 1193 return rd; 1194 } 1195 1196 public void recycle() { 1197 sPool.release(this); 1198 } 1199 } 1200 1201 private void setDrawableTint(int id, ColorStateList tint, Mode tintMode, boolean fallback) { 1202 Drawable layer = null; 1203 1204 // We expect a layer drawable, so try to find the target ID. 1205 final Drawable d = mCurrentDrawable; 1206 if (d instanceof LayerDrawable) { 1207 layer = ((LayerDrawable) d).findDrawableByLayerId(id); 1208 } 1209 1210 if (fallback && layer == null) { 1211 layer = d; 1212 } 1213 1214 layer.mutate(); 1215 layer.setTintList(tint); 1216 layer.setTintMode(tintMode); 1217 } 1218 1219 private float getScale(float progress) { 1220 return mMax > 0 ? progress / (float) mMax : 0; 1221 } 1222 1223 private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, 1224 boolean callBackToApp) { 1225 doRefreshProgress(id, progress, fromUser, callBackToApp, false); 1226 } 1227 1228 private synchronized void doRefreshProgress(int id, float progress, boolean fromUser, 1229 boolean callBackToApp, boolean animate) { 1230 float scale = getScale(progress); 1231 1232 final Drawable d = mCurrentDrawable; 1233 if (d != null) { 1234 Drawable progressDrawable = null; 1235 1236 if (d instanceof LayerDrawable) { 1237 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id); 1238 if (progressDrawable != null && canResolveLayoutDirection()) { 1239 progressDrawable.setLayoutDirection(getLayoutDirection()); 1240 } 1241 } 1242 1243 final int level = (int) (scale * MAX_LEVEL); 1244 (progressDrawable != null ? progressDrawable : d).setLevel(level); 1245 } else { 1246 invalidate(); 1247 } 1248 1249 if (id == R.id.progress) { 1250 if (animate) { 1251 onAnimatePosition(scale, fromUser); 1252 } else if (callBackToApp) { 1253 onProgressRefresh(scale, fromUser); 1254 } 1255 } 1256 } 1257 1258 /** 1259 * Called when a ProgressBar is animating its position. 1260 * 1261 * @param scale Current position/progress between 0 and 1. 1262 * @param fromUser True if the progress change was initiated by the user. 1263 */ 1264 void onAnimatePosition(float scale, boolean fromUser) { 1265 } 1266 1267 /** 1268 * Sets the progress value without going through the entire refresh process. 1269 * 1270 * @see #setProgress(int, boolean) 1271 * @param progress The new progress, between 0 and {@link #getMax()} 1272 */ 1273 void setProgressValueOnly(int progress) { 1274 mProgress = progress; 1275 onProgressRefresh(getScale(progress), true); 1276 } 1277 1278 void setAnimationPosition(float position) { 1279 mAnimationPosition = position; 1280 refreshProgress(R.id.progress, position, true, true); 1281 } 1282 1283 float getAnimationPosition() { 1284 return mAnimationPosition; 1285 } 1286 1287 void onProgressRefresh(float scale, boolean fromUser) { 1288 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 1289 scheduleAccessibilityEventSender(); 1290 } 1291 } 1292 1293 private synchronized void refreshProgress(int id, float progress, boolean fromUser) { 1294 refreshProgress(id, progress, fromUser, false); 1295 } 1296 1297 private synchronized void refreshProgress(int id, float progress, boolean fromUser, 1298 boolean animate) { 1299 if (mUiThreadId == Thread.currentThread().getId()) { 1300 doRefreshProgress(id, progress, fromUser, true, animate); 1301 } else { 1302 if (mRefreshProgressRunnable == null) { 1303 mRefreshProgressRunnable = new RefreshProgressRunnable(); 1304 } 1305 1306 final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); 1307 mRefreshData.add(rd); 1308 if (mAttached && !mRefreshIsPosted) { 1309 post(mRefreshProgressRunnable); 1310 mRefreshIsPosted = true; 1311 } 1312 } 1313 } 1314 1315 /** 1316 * <p>Set the current progress to the specified value. Does not do anything 1317 * if the progress bar is in indeterminate mode.</p> 1318 * 1319 * @param progress the new progress, between 0 and {@link #getMax()} 1320 * 1321 * @see #setIndeterminate(boolean) 1322 * @see #isIndeterminate() 1323 * @see #getProgress() 1324 * @see #incrementProgressBy(int) 1325 */ 1326 @android.view.RemotableViewMethod 1327 public synchronized void setProgress(int progress) { 1328 setProgress(progress, false); 1329 } 1330 1331 @android.view.RemotableViewMethod 1332 synchronized void setProgress(int progress, boolean fromUser) { 1333 if (mIndeterminate) { 1334 return; 1335 } 1336 1337 if (progress < 0) { 1338 progress = 0; 1339 } 1340 1341 if (progress > mMax) { 1342 progress = mMax; 1343 } 1344 1345 if (progress != mProgress) { 1346 mProgress = progress; 1347 refreshProgress(R.id.progress, mProgress, fromUser); 1348 } 1349 } 1350 1351 /** 1352 * <p> 1353 * Set the current secondary progress to the specified value. Does not do 1354 * anything if the progress bar is in indeterminate mode. 1355 * </p> 1356 * 1357 * @param secondaryProgress the new secondary progress, between 0 and {@link #getMax()} 1358 * @see #setIndeterminate(boolean) 1359 * @see #isIndeterminate() 1360 * @see #getSecondaryProgress() 1361 * @see #incrementSecondaryProgressBy(int) 1362 */ 1363 @android.view.RemotableViewMethod 1364 public synchronized void setSecondaryProgress(int secondaryProgress) { 1365 if (mIndeterminate) { 1366 return; 1367 } 1368 1369 if (secondaryProgress < 0) { 1370 secondaryProgress = 0; 1371 } 1372 1373 if (secondaryProgress > mMax) { 1374 secondaryProgress = mMax; 1375 } 1376 1377 if (secondaryProgress != mSecondaryProgress) { 1378 mSecondaryProgress = secondaryProgress; 1379 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false); 1380 } 1381 } 1382 1383 /** 1384 * <p>Get the progress bar's current level of progress. Return 0 when the 1385 * progress bar is in indeterminate mode.</p> 1386 * 1387 * @return the current progress, between 0 and {@link #getMax()} 1388 * 1389 * @see #setIndeterminate(boolean) 1390 * @see #isIndeterminate() 1391 * @see #setProgress(int) 1392 * @see #setMax(int) 1393 * @see #getMax() 1394 */ 1395 @ViewDebug.ExportedProperty(category = "progress") 1396 public synchronized int getProgress() { 1397 return mIndeterminate ? 0 : mProgress; 1398 } 1399 1400 /** 1401 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 1402 * progress bar is in indeterminate mode.</p> 1403 * 1404 * @return the current secondary progress, between 0 and {@link #getMax()} 1405 * 1406 * @see #setIndeterminate(boolean) 1407 * @see #isIndeterminate() 1408 * @see #setSecondaryProgress(int) 1409 * @see #setMax(int) 1410 * @see #getMax() 1411 */ 1412 @ViewDebug.ExportedProperty(category = "progress") 1413 public synchronized int getSecondaryProgress() { 1414 return mIndeterminate ? 0 : mSecondaryProgress; 1415 } 1416 1417 /** 1418 * <p>Return the upper limit of this progress bar's range.</p> 1419 * 1420 * @return a positive integer 1421 * 1422 * @see #setMax(int) 1423 * @see #getProgress() 1424 * @see #getSecondaryProgress() 1425 */ 1426 @ViewDebug.ExportedProperty(category = "progress") 1427 public synchronized int getMax() { 1428 return mMax; 1429 } 1430 1431 /** 1432 * <p>Set the range of the progress bar to 0...<tt>max</tt>.</p> 1433 * 1434 * @param max the upper range of this progress bar 1435 * 1436 * @see #getMax() 1437 * @see #setProgress(int) 1438 * @see #setSecondaryProgress(int) 1439 */ 1440 @android.view.RemotableViewMethod 1441 public synchronized void setMax(int max) { 1442 if (max < 0) { 1443 max = 0; 1444 } 1445 if (max != mMax) { 1446 mMax = max; 1447 postInvalidate(); 1448 1449 if (mProgress > max) { 1450 mProgress = max; 1451 } 1452 refreshProgress(R.id.progress, mProgress, false); 1453 } 1454 } 1455 1456 /** 1457 * <p>Increase the progress bar's progress by the specified amount.</p> 1458 * 1459 * @param diff the amount by which the progress must be increased 1460 * 1461 * @see #setProgress(int) 1462 */ 1463 public synchronized final void incrementProgressBy(int diff) { 1464 setProgress(mProgress + diff); 1465 } 1466 1467 /** 1468 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 1469 * 1470 * @param diff the amount by which the secondary progress must be increased 1471 * 1472 * @see #setSecondaryProgress(int) 1473 */ 1474 public synchronized final void incrementSecondaryProgressBy(int diff) { 1475 setSecondaryProgress(mSecondaryProgress + diff); 1476 } 1477 1478 /** 1479 * <p>Start the indeterminate progress animation.</p> 1480 */ 1481 void startAnimation() { 1482 if (getVisibility() != VISIBLE) { 1483 return; 1484 } 1485 1486 if (mIndeterminateDrawable instanceof Animatable) { 1487 mShouldStartAnimationDrawable = true; 1488 mHasAnimation = false; 1489 } else { 1490 mHasAnimation = true; 1491 1492 if (mInterpolator == null) { 1493 mInterpolator = new LinearInterpolator(); 1494 } 1495 1496 if (mTransformation == null) { 1497 mTransformation = new Transformation(); 1498 } else { 1499 mTransformation.clear(); 1500 } 1501 1502 if (mAnimation == null) { 1503 mAnimation = new AlphaAnimation(0.0f, 1.0f); 1504 } else { 1505 mAnimation.reset(); 1506 } 1507 1508 mAnimation.setRepeatMode(mBehavior); 1509 mAnimation.setRepeatCount(Animation.INFINITE); 1510 mAnimation.setDuration(mDuration); 1511 mAnimation.setInterpolator(mInterpolator); 1512 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 1513 } 1514 postInvalidate(); 1515 } 1516 1517 /** 1518 * <p>Stop the indeterminate progress animation.</p> 1519 */ 1520 void stopAnimation() { 1521 mHasAnimation = false; 1522 if (mIndeterminateDrawable instanceof Animatable) { 1523 ((Animatable) mIndeterminateDrawable).stop(); 1524 mShouldStartAnimationDrawable = false; 1525 } 1526 postInvalidate(); 1527 } 1528 1529 /** 1530 * Sets the acceleration curve for the indeterminate animation. 1531 * The interpolator is loaded as a resource from the specified context. 1532 * 1533 * @param context The application environment 1534 * @param resID The resource identifier of the interpolator to load 1535 */ 1536 public void setInterpolator(Context context, int resID) { 1537 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 1538 } 1539 1540 /** 1541 * Sets the acceleration curve for the indeterminate animation. 1542 * Defaults to a linear interpolation. 1543 * 1544 * @param interpolator The interpolator which defines the acceleration curve 1545 */ 1546 public void setInterpolator(Interpolator interpolator) { 1547 mInterpolator = interpolator; 1548 } 1549 1550 /** 1551 * Gets the acceleration curve type for the indeterminate animation. 1552 * 1553 * @return the {@link Interpolator} associated to this animation 1554 */ 1555 public Interpolator getInterpolator() { 1556 return mInterpolator; 1557 } 1558 1559 @Override 1560 @RemotableViewMethod 1561 public void setVisibility(int v) { 1562 if (getVisibility() != v) { 1563 super.setVisibility(v); 1564 1565 if (mIndeterminate) { 1566 // let's be nice with the UI thread 1567 if (v == GONE || v == INVISIBLE) { 1568 stopAnimation(); 1569 } else { 1570 startAnimation(); 1571 } 1572 } 1573 } 1574 } 1575 1576 @Override 1577 protected void onVisibilityChanged(View changedView, int visibility) { 1578 super.onVisibilityChanged(changedView, visibility); 1579 1580 if (mIndeterminate) { 1581 // let's be nice with the UI thread 1582 if (visibility == GONE || visibility == INVISIBLE) { 1583 stopAnimation(); 1584 } else { 1585 startAnimation(); 1586 } 1587 } 1588 } 1589 1590 @Override 1591 public void invalidateDrawable(Drawable dr) { 1592 if (!mInDrawing) { 1593 if (verifyDrawable(dr)) { 1594 final Rect dirty = dr.getBounds(); 1595 final int scrollX = mScrollX + mPaddingLeft; 1596 final int scrollY = mScrollY + mPaddingTop; 1597 1598 invalidate(dirty.left + scrollX, dirty.top + scrollY, 1599 dirty.right + scrollX, dirty.bottom + scrollY); 1600 } else { 1601 super.invalidateDrawable(dr); 1602 } 1603 } 1604 } 1605 1606 @Override 1607 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1608 updateDrawableBounds(w, h); 1609 } 1610 1611 private void updateDrawableBounds(int w, int h) { 1612 // onDraw will translate the canvas so we draw starting at 0,0. 1613 // Subtract out padding for the purposes of the calculations below. 1614 w -= mPaddingRight + mPaddingLeft; 1615 h -= mPaddingTop + mPaddingBottom; 1616 1617 int right = w; 1618 int bottom = h; 1619 int top = 0; 1620 int left = 0; 1621 1622 if (mIndeterminateDrawable != null) { 1623 // Aspect ratio logic does not apply to AnimationDrawables 1624 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 1625 // Maintain aspect ratio. Certain kinds of animated drawables 1626 // get very confused otherwise. 1627 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 1628 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 1629 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 1630 final float boundAspect = (float) w / h; 1631 if (intrinsicAspect != boundAspect) { 1632 if (boundAspect > intrinsicAspect) { 1633 // New width is larger. Make it smaller to match height. 1634 final int width = (int) (h * intrinsicAspect); 1635 left = (w - width) / 2; 1636 right = left + width; 1637 } else { 1638 // New height is larger. Make it smaller to match width. 1639 final int height = (int) (w * (1 / intrinsicAspect)); 1640 top = (h - height) / 2; 1641 bottom = top + height; 1642 } 1643 } 1644 } 1645 if (isLayoutRtl() && mMirrorForRtl) { 1646 int tempLeft = left; 1647 left = w - right; 1648 right = w - tempLeft; 1649 } 1650 mIndeterminateDrawable.setBounds(left, top, right, bottom); 1651 } 1652 1653 if (mProgressDrawable != null) { 1654 mProgressDrawable.setBounds(0, 0, right, bottom); 1655 } 1656 } 1657 1658 @Override 1659 protected synchronized void onDraw(Canvas canvas) { 1660 super.onDraw(canvas); 1661 1662 drawTrack(canvas); 1663 } 1664 1665 /** 1666 * Draws the progress bar track. 1667 */ 1668 void drawTrack(Canvas canvas) { 1669 final Drawable d = mCurrentDrawable; 1670 if (d != null) { 1671 // Translate canvas so a indeterminate circular progress bar with padding 1672 // rotates properly in its animation 1673 final int saveCount = canvas.save(); 1674 1675 if(isLayoutRtl() && mMirrorForRtl) { 1676 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 1677 canvas.scale(-1.0f, 1.0f); 1678 } else { 1679 canvas.translate(mPaddingLeft, mPaddingTop); 1680 } 1681 1682 final long time = getDrawingTime(); 1683 if (mHasAnimation) { 1684 mAnimation.getTransformation(time, mTransformation); 1685 final float scale = mTransformation.getAlpha(); 1686 try { 1687 mInDrawing = true; 1688 d.setLevel((int) (scale * MAX_LEVEL)); 1689 } finally { 1690 mInDrawing = false; 1691 } 1692 postInvalidateOnAnimation(); 1693 } 1694 1695 d.draw(canvas); 1696 canvas.restoreToCount(saveCount); 1697 1698 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 1699 ((Animatable) d).start(); 1700 mShouldStartAnimationDrawable = false; 1701 } 1702 } 1703 } 1704 1705 @Override 1706 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1707 Drawable d = mCurrentDrawable; 1708 1709 int dw = 0; 1710 int dh = 0; 1711 if (d != null) { 1712 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 1713 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 1714 } 1715 updateDrawableState(); 1716 dw += mPaddingLeft + mPaddingRight; 1717 dh += mPaddingTop + mPaddingBottom; 1718 1719 setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0), 1720 resolveSizeAndState(dh, heightMeasureSpec, 0)); 1721 } 1722 1723 @Override 1724 protected void drawableStateChanged() { 1725 super.drawableStateChanged(); 1726 updateDrawableState(); 1727 } 1728 1729 private void updateDrawableState() { 1730 int[] state = getDrawableState(); 1731 1732 if (mProgressDrawable != null && mProgressDrawable.isStateful()) { 1733 mProgressDrawable.setState(state); 1734 } 1735 1736 if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { 1737 mIndeterminateDrawable.setState(state); 1738 } 1739 } 1740 1741 @Override 1742 public void drawableHotspotChanged(float x, float y) { 1743 super.drawableHotspotChanged(x, y); 1744 1745 if (mProgressDrawable != null) { 1746 mProgressDrawable.setHotspot(x, y); 1747 } 1748 1749 if (mIndeterminateDrawable != null) { 1750 mIndeterminateDrawable.setHotspot(x, y); 1751 } 1752 } 1753 1754 static class SavedState extends BaseSavedState { 1755 int progress; 1756 int secondaryProgress; 1757 1758 /** 1759 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 1760 */ 1761 SavedState(Parcelable superState) { 1762 super(superState); 1763 } 1764 1765 /** 1766 * Constructor called from {@link #CREATOR} 1767 */ 1768 private SavedState(Parcel in) { 1769 super(in); 1770 progress = in.readInt(); 1771 secondaryProgress = in.readInt(); 1772 } 1773 1774 @Override 1775 public void writeToParcel(Parcel out, int flags) { 1776 super.writeToParcel(out, flags); 1777 out.writeInt(progress); 1778 out.writeInt(secondaryProgress); 1779 } 1780 1781 public static final Parcelable.Creator<SavedState> CREATOR 1782 = new Parcelable.Creator<SavedState>() { 1783 public SavedState createFromParcel(Parcel in) { 1784 return new SavedState(in); 1785 } 1786 1787 public SavedState[] newArray(int size) { 1788 return new SavedState[size]; 1789 } 1790 }; 1791 } 1792 1793 @Override 1794 public Parcelable onSaveInstanceState() { 1795 // Force our ancestor class to save its state 1796 Parcelable superState = super.onSaveInstanceState(); 1797 SavedState ss = new SavedState(superState); 1798 1799 ss.progress = mProgress; 1800 ss.secondaryProgress = mSecondaryProgress; 1801 1802 return ss; 1803 } 1804 1805 @Override 1806 public void onRestoreInstanceState(Parcelable state) { 1807 SavedState ss = (SavedState) state; 1808 super.onRestoreInstanceState(ss.getSuperState()); 1809 1810 setProgress(ss.progress); 1811 setSecondaryProgress(ss.secondaryProgress); 1812 } 1813 1814 @Override 1815 protected void onAttachedToWindow() { 1816 super.onAttachedToWindow(); 1817 if (mIndeterminate) { 1818 startAnimation(); 1819 } 1820 if (mRefreshData != null) { 1821 synchronized (this) { 1822 final int count = mRefreshData.size(); 1823 for (int i = 0; i < count; i++) { 1824 final RefreshData rd = mRefreshData.get(i); 1825 doRefreshProgress(rd.id, rd.progress, rd.fromUser, rd.animate); 1826 rd.recycle(); 1827 } 1828 mRefreshData.clear(); 1829 } 1830 } 1831 mAttached = true; 1832 } 1833 1834 @Override 1835 protected void onDetachedFromWindow() { 1836 if (mIndeterminate) { 1837 stopAnimation(); 1838 } 1839 if (mRefreshProgressRunnable != null) { 1840 removeCallbacks(mRefreshProgressRunnable); 1841 } 1842 if (mRefreshProgressRunnable != null && mRefreshIsPosted) { 1843 removeCallbacks(mRefreshProgressRunnable); 1844 } 1845 if (mAccessibilityEventSender != null) { 1846 removeCallbacks(mAccessibilityEventSender); 1847 } 1848 // This should come after stopAnimation(), otherwise an invalidate message remains in the 1849 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 1850 super.onDetachedFromWindow(); 1851 mAttached = false; 1852 } 1853 1854 @Override 1855 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 1856 super.onInitializeAccessibilityEvent(event); 1857 event.setClassName(ProgressBar.class.getName()); 1858 event.setItemCount(mMax); 1859 event.setCurrentItemIndex(mProgress); 1860 } 1861 1862 @Override 1863 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 1864 super.onInitializeAccessibilityNodeInfo(info); 1865 info.setClassName(ProgressBar.class.getName()); 1866 } 1867 1868 /** 1869 * Schedule a command for sending an accessibility event. 1870 * </br> 1871 * Note: A command is used to ensure that accessibility events 1872 * are sent at most one in a given time frame to save 1873 * system resources while the progress changes quickly. 1874 */ 1875 private void scheduleAccessibilityEventSender() { 1876 if (mAccessibilityEventSender == null) { 1877 mAccessibilityEventSender = new AccessibilityEventSender(); 1878 } else { 1879 removeCallbacks(mAccessibilityEventSender); 1880 } 1881 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 1882 } 1883 1884 /** 1885 * Command for sending an accessibility event. 1886 */ 1887 private class AccessibilityEventSender implements Runnable { 1888 public void run() { 1889 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 1890 } 1891 } 1892 1893 private static class ProgressTintInfo { 1894 ColorStateList mIndeterminateTintList; 1895 PorterDuff.Mode mIndeterminateTintMode; 1896 boolean mHasIndeterminateTint; 1897 boolean mHasIndeterminateTintMode; 1898 1899 ColorStateList mProgressTintList; 1900 PorterDuff.Mode mProgressTintMode; 1901 boolean mHasProgressTint; 1902 boolean mHasProgressTintMode; 1903 1904 ColorStateList mProgressBackgroundTintList; 1905 PorterDuff.Mode mProgressBackgroundTintMode; 1906 boolean mHasProgressBackgroundTint; 1907 boolean mHasProgressBackgroundTintMode; 1908 1909 ColorStateList mSecondaryProgressTintList; 1910 PorterDuff.Mode mSecondaryProgressTintMode; 1911 boolean mHasSecondaryProgressTint; 1912 boolean mHasSecondaryProgressTintMode; 1913 } 1914} 1915