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