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