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.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.annotation.InterpolatorRes; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.Px; 26 import android.compat.annotation.UnsupportedAppUsage; 27 import android.content.Context; 28 import android.content.res.ColorStateList; 29 import android.content.res.TypedArray; 30 import android.graphics.BlendMode; 31 import android.graphics.Canvas; 32 import android.graphics.PorterDuff; 33 import android.graphics.Rect; 34 import android.graphics.Shader; 35 import android.graphics.drawable.Animatable; 36 import android.graphics.drawable.AnimationDrawable; 37 import android.graphics.drawable.BitmapDrawable; 38 import android.graphics.drawable.ClipDrawable; 39 import android.graphics.drawable.Drawable; 40 import android.graphics.drawable.LayerDrawable; 41 import android.graphics.drawable.StateListDrawable; 42 import android.graphics.drawable.shapes.RoundRectShape; 43 import android.graphics.drawable.shapes.Shape; 44 import android.os.Build; 45 import android.os.Parcel; 46 import android.os.Parcelable; 47 import android.util.AttributeSet; 48 import android.util.FloatProperty; 49 import android.util.MathUtils; 50 import android.util.Pools.SynchronizedPool; 51 import android.view.Gravity; 52 import android.view.RemotableViewMethod; 53 import android.view.View; 54 import android.view.ViewDebug; 55 import android.view.ViewHierarchyEncoder; 56 import android.view.accessibility.AccessibilityEvent; 57 import android.view.accessibility.AccessibilityManager; 58 import android.view.accessibility.AccessibilityNodeInfo; 59 import android.view.animation.AlphaAnimation; 60 import android.view.animation.Animation; 61 import android.view.animation.AnimationUtils; 62 import android.view.animation.DecelerateInterpolator; 63 import android.view.animation.Interpolator; 64 import android.view.animation.LinearInterpolator; 65 import android.view.animation.Transformation; 66 import android.view.inspector.InspectableProperty; 67 import android.widget.RemoteViews.RemoteView; 68 69 import com.android.internal.R; 70 71 import java.text.NumberFormat; 72 import java.util.ArrayList; 73 import java.util.Locale; 74 75 /** 76 * <p> 77 * A user interface element that indicates the progress of an operation. 78 * Progress bar supports two modes to represent progress: determinate, and indeterminate. For 79 * a visual overview of the difference between determinate and indeterminate progress modes, see 80 * <a href="https://material.io/guidelines/components/progress-activity.html#progress-activity-types-of-indicators"> 81 * Progress & activity</a>. 82 * Display progress bars to a user in a non-interruptive way. 83 * Show the progress bar in your app's user interface or in a notification 84 * instead of within a dialog. 85 * </p> 86 * <h3>Indeterminate Progress</h3> 87 * <p> 88 * Use indeterminate mode for the progress bar when you do not know how long an 89 * operation will take. 90 * Indeterminate mode is the default for progress bar and shows a cyclic animation without a 91 * specific amount of progress indicated. 92 * The following example shows an indeterminate progress bar: 93 * <pre> 94 * <ProgressBar 95 * android:id="@+id/indeterminateBar" 96 * android:layout_width="wrap_content" 97 * android:layout_height="wrap_content" 98 * /> 99 * </pre> 100 * </p> 101 * <h3>Determinate Progress</h3> 102 * <p> 103 * Use determinate mode for the progress bar when you want to show that a specific quantity of 104 * progress has occurred. 105 * For example, the percent remaining of a file being retrieved, the amount records in 106 * a batch written to database, or the percent remaining of an audio file that is playing. 107 * <p> 108 * <p> 109 * To indicate determinate progress, you set the style of the progress bar to 110 * {@link android.R.style#Widget_ProgressBar_Horizontal} and set the amount of progress. 111 * The following example shows a determinate progress bar that is 25% complete: 112 * <pre> 113 * <ProgressBar 114 * android:id="@+id/determinateBar" 115 * style="@android:style/Widget.ProgressBar.Horizontal" 116 * android:layout_width="wrap_content" 117 * android:layout_height="wrap_content" 118 * android:progress="25"/> 119 * </pre> 120 * You can update the percentage of progress displayed by using the 121 * {@link #setProgress(int)} method, or by calling 122 * {@link #incrementProgressBy(int)} to increase the current progress completed 123 * by a specified amount. 124 * By default, the progress bar is full when the progress value reaches 100. 125 * You can adjust this default by setting the 126 * {@link android.R.styleable#ProgressBar_max android:max} attribute. 127 * </p> 128 * <p>Other progress bar styles provided by the system include:</p> 129 * <ul> 130 * <li>{@link android.R.style#Widget_ProgressBar_Horizontal Widget.ProgressBar.Horizontal}</li> 131 * <li>{@link android.R.style#Widget_ProgressBar_Small Widget.ProgressBar.Small}</li> 132 * <li>{@link android.R.style#Widget_ProgressBar_Large Widget.ProgressBar.Large}</li> 133 * <li>{@link android.R.style#Widget_ProgressBar_Inverse Widget.ProgressBar.Inverse}</li> 134 * <li>{@link android.R.style#Widget_ProgressBar_Small_Inverse 135 * Widget.ProgressBar.Small.Inverse}</li> 136 * <li>{@link android.R.style#Widget_ProgressBar_Large_Inverse 137 * Widget.ProgressBar.Large.Inverse}</li> 138 * </ul> 139 * <p>The "inverse" styles provide an inverse color scheme for the spinner, which may be necessary 140 * if your application uses a light colored theme (a white background).</p> 141 * 142 * <p><strong>XML attributes</b></strong> 143 * <p> 144 * See {@link android.R.styleable#ProgressBar ProgressBar Attributes}, 145 * {@link android.R.styleable#View View Attributes} 146 * </p> 147 * 148 * @attr ref android.R.styleable#ProgressBar_animationResolution 149 * @attr ref android.R.styleable#ProgressBar_indeterminate 150 * @attr ref android.R.styleable#ProgressBar_indeterminateBehavior 151 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable 152 * @attr ref android.R.styleable#ProgressBar_indeterminateDuration 153 * @attr ref android.R.styleable#ProgressBar_indeterminateOnly 154 * @attr ref android.R.styleable#ProgressBar_interpolator 155 * @attr ref android.R.styleable#ProgressBar_min 156 * @attr ref android.R.styleable#ProgressBar_max 157 * @attr ref android.R.styleable#ProgressBar_maxHeight 158 * @attr ref android.R.styleable#ProgressBar_maxWidth 159 * @attr ref android.R.styleable#ProgressBar_minHeight 160 * @attr ref android.R.styleable#ProgressBar_minWidth 161 * @attr ref android.R.styleable#ProgressBar_mirrorForRtl 162 * @attr ref android.R.styleable#ProgressBar_progress 163 * @attr ref android.R.styleable#ProgressBar_progressDrawable 164 * @attr ref android.R.styleable#ProgressBar_secondaryProgress 165 */ 166 @RemoteView 167 public class ProgressBar extends View { 168 169 private static final int MAX_LEVEL = 10000; 170 171 /** Interpolator used for smooth progress animations. */ 172 private static final DecelerateInterpolator PROGRESS_ANIM_INTERPOLATOR = 173 new DecelerateInterpolator(); 174 175 /** Duration of smooth progress animations. */ 176 private static final int PROGRESS_ANIM_DURATION = 80; 177 178 /** 179 * Outside the framework, please use {@link ProgressBar#getMinWidth()} and 180 * {@link ProgressBar#setMinWidth(int)} instead of accessing these directly. 181 */ 182 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 183 int mMinWidth; 184 int mMaxWidth; 185 /** 186 * Outside the framework, please use {@link ProgressBar#getMinHeight()} and 187 * {@link ProgressBar#setMinHeight(int)} instead of accessing these directly. 188 */ 189 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 190 int mMinHeight; 191 /** 192 * Outside the framework, please use {@link ProgressBar#getMaxHeight()} ()} and 193 * {@link ProgressBar#setMaxHeight(int)} (int)} instead of accessing these directly. 194 */ 195 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 196 int mMaxHeight; 197 198 private int mProgress; 199 private int mSecondaryProgress; 200 private int mMin; 201 private boolean mMinInitialized; 202 private int mMax; 203 private boolean mMaxInitialized; 204 205 private int mBehavior; 206 // Better to define a Drawable that implements Animatable if you want to modify animation 207 // characteristics programatically. 208 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124052713) 209 private int mDuration; 210 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 211 private boolean mIndeterminate; 212 @UnsupportedAppUsage(trackingBug = 124049927) 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 /** 221 * Outside the framework, instead of accessing this directly, please use 222 * {@link #getCurrentDrawable()}, {@link #setProgressDrawable(Drawable)}, 223 * {@link #setIndeterminateDrawable(Drawable)} and their tiled versions. 224 */ 225 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 226 private Drawable mCurrentDrawable; 227 private ProgressTintInfo mProgressTintInfo; 228 229 int mSampleWidth = 0; 230 private boolean mNoInvalidate; 231 private Interpolator mInterpolator; 232 private RefreshProgressRunnable mRefreshProgressRunnable; 233 private long mUiThreadId; 234 private boolean mShouldStartAnimationDrawable; 235 236 private boolean mInDrawing; 237 private boolean mAttached; 238 private boolean mRefreshIsPosted; 239 240 /** Value used to track progress animation, in the range [0...1]. */ 241 private float mVisualProgress; 242 243 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 244 boolean mMirrorForRtl = false; 245 246 private boolean mAggregatedIsVisible; 247 248 private CharSequence mCustomStateDescription = null; 249 250 private final ArrayList<RefreshData> mRefreshData = new ArrayList<RefreshData>(); 251 252 private ObjectAnimator mLastProgressAnimator; 253 254 private NumberFormat mPercentFormat; 255 private Locale mCachedLocale; 256 257 /** 258 * Create a new progress bar with range 0...100 and initial progress of 0. 259 * @param context the application environment 260 */ ProgressBar(Context context)261 public ProgressBar(Context context) { 262 this(context, null); 263 } 264 ProgressBar(Context context, AttributeSet attrs)265 public ProgressBar(Context context, AttributeSet attrs) { 266 this(context, attrs, com.android.internal.R.attr.progressBarStyle); 267 } 268 ProgressBar(Context context, AttributeSet attrs, int defStyleAttr)269 public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { 270 this(context, attrs, defStyleAttr, 0); 271 } 272 ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)273 public ProgressBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 274 super(context, attrs, defStyleAttr, defStyleRes); 275 276 mUiThreadId = Thread.currentThread().getId(); 277 initProgressBar(); 278 279 final TypedArray a = context.obtainStyledAttributes( 280 attrs, R.styleable.ProgressBar, defStyleAttr, defStyleRes); 281 saveAttributeDataForStyleable(context, R.styleable.ProgressBar, 282 attrs, a, defStyleAttr, defStyleRes); 283 284 mNoInvalidate = true; 285 286 final Drawable progressDrawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); 287 if (progressDrawable != null) { 288 // Calling setProgressDrawable can set mMaxHeight, so make sure the 289 // corresponding XML attribute for mMaxHeight is read after calling 290 // this method. 291 if (needsTileify(progressDrawable)) { 292 setProgressDrawableTiled(progressDrawable); 293 } else { 294 setProgressDrawable(progressDrawable); 295 } 296 } 297 298 mDuration = a.getInt(R.styleable.ProgressBar_indeterminateDuration, mDuration); 299 300 mMinWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_minWidth, mMinWidth); 301 mMaxWidth = a.getDimensionPixelSize(R.styleable.ProgressBar_maxWidth, mMaxWidth); 302 mMinHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_minHeight, mMinHeight); 303 mMaxHeight = a.getDimensionPixelSize(R.styleable.ProgressBar_maxHeight, mMaxHeight); 304 305 mBehavior = a.getInt(R.styleable.ProgressBar_indeterminateBehavior, mBehavior); 306 307 final int resID = a.getResourceId( 308 com.android.internal.R.styleable.ProgressBar_interpolator, 309 android.R.anim.linear_interpolator); // default to linear interpolator 310 if (resID > 0) { 311 setInterpolator(context, resID); 312 } 313 314 setMin(a.getInt(R.styleable.ProgressBar_min, mMin)); 315 setMax(a.getInt(R.styleable.ProgressBar_max, mMax)); 316 317 setProgress(a.getInt(R.styleable.ProgressBar_progress, mProgress)); 318 319 setSecondaryProgress(a.getInt( 320 R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); 321 322 final Drawable indeterminateDrawable = a.getDrawable( 323 R.styleable.ProgressBar_indeterminateDrawable); 324 if (indeterminateDrawable != null) { 325 if (needsTileify(indeterminateDrawable)) { 326 setIndeterminateDrawableTiled(indeterminateDrawable); 327 } else { 328 setIndeterminateDrawable(indeterminateDrawable); 329 } 330 } 331 332 mOnlyIndeterminate = a.getBoolean( 333 R.styleable.ProgressBar_indeterminateOnly, mOnlyIndeterminate); 334 335 mNoInvalidate = false; 336 337 setIndeterminate(mOnlyIndeterminate || a.getBoolean( 338 R.styleable.ProgressBar_indeterminate, mIndeterminate)); 339 340 mMirrorForRtl = a.getBoolean(R.styleable.ProgressBar_mirrorForRtl, mMirrorForRtl); 341 342 if (a.hasValue(R.styleable.ProgressBar_progressTintMode)) { 343 if (mProgressTintInfo == null) { 344 mProgressTintInfo = new ProgressTintInfo(); 345 } 346 mProgressTintInfo.mProgressBlendMode = Drawable.parseBlendMode(a.getInt( 347 R.styleable.ProgressBar_progressTintMode, -1), null); 348 mProgressTintInfo.mHasProgressTintMode = true; 349 } 350 351 if (a.hasValue(R.styleable.ProgressBar_progressTint)) { 352 if (mProgressTintInfo == null) { 353 mProgressTintInfo = new ProgressTintInfo(); 354 } 355 mProgressTintInfo.mProgressTintList = a.getColorStateList( 356 R.styleable.ProgressBar_progressTint); 357 mProgressTintInfo.mHasProgressTint = true; 358 } 359 360 if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTintMode)) { 361 if (mProgressTintInfo == null) { 362 mProgressTintInfo = new ProgressTintInfo(); 363 } 364 mProgressTintInfo.mProgressBackgroundBlendMode = Drawable.parseBlendMode(a.getInt( 365 R.styleable.ProgressBar_progressBackgroundTintMode, -1), null); 366 mProgressTintInfo.mHasProgressBackgroundTintMode = true; 367 } 368 369 if (a.hasValue(R.styleable.ProgressBar_progressBackgroundTint)) { 370 if (mProgressTintInfo == null) { 371 mProgressTintInfo = new ProgressTintInfo(); 372 } 373 mProgressTintInfo.mProgressBackgroundTintList = a.getColorStateList( 374 R.styleable.ProgressBar_progressBackgroundTint); 375 mProgressTintInfo.mHasProgressBackgroundTint = true; 376 } 377 378 if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTintMode)) { 379 if (mProgressTintInfo == null) { 380 mProgressTintInfo = new ProgressTintInfo(); 381 } 382 mProgressTintInfo.mSecondaryProgressBlendMode = Drawable.parseBlendMode( 383 a.getInt(R.styleable.ProgressBar_secondaryProgressTintMode, -1), null); 384 mProgressTintInfo.mHasSecondaryProgressTintMode = true; 385 } 386 387 if (a.hasValue(R.styleable.ProgressBar_secondaryProgressTint)) { 388 if (mProgressTintInfo == null) { 389 mProgressTintInfo = new ProgressTintInfo(); 390 } 391 mProgressTintInfo.mSecondaryProgressTintList = a.getColorStateList( 392 R.styleable.ProgressBar_secondaryProgressTint); 393 mProgressTintInfo.mHasSecondaryProgressTint = true; 394 } 395 396 if (a.hasValue(R.styleable.ProgressBar_indeterminateTintMode)) { 397 if (mProgressTintInfo == null) { 398 mProgressTintInfo = new ProgressTintInfo(); 399 } 400 mProgressTintInfo.mIndeterminateBlendMode = Drawable.parseBlendMode(a.getInt( 401 R.styleable.ProgressBar_indeterminateTintMode, -1), null); 402 mProgressTintInfo.mHasIndeterminateTintMode = true; 403 } 404 405 if (a.hasValue(R.styleable.ProgressBar_indeterminateTint)) { 406 if (mProgressTintInfo == null) { 407 mProgressTintInfo = new ProgressTintInfo(); 408 } 409 mProgressTintInfo.mIndeterminateTintList = a.getColorStateList( 410 R.styleable.ProgressBar_indeterminateTint); 411 mProgressTintInfo.mHasIndeterminateTint = true; 412 } 413 414 a.recycle(); 415 416 applyProgressTints(); 417 applyIndeterminateTint(); 418 419 // If not explicitly specified this view is important for accessibility. 420 if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 421 setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); 422 } 423 } 424 425 /** 426 * Sets the minimum width the progress bar can have. 427 * @param minWidth the minimum width to be set, in pixels 428 * @attr ref android.R.styleable#ProgressBar_minWidth 429 */ setMinWidth(@x int minWidth)430 public void setMinWidth(@Px int minWidth) { 431 mMinWidth = minWidth; 432 requestLayout(); 433 } 434 435 /** 436 * @return the minimum width the progress bar can have, in pixels 437 */ getMinWidth()438 @Px public int getMinWidth() { 439 return mMinWidth; 440 } 441 442 /** 443 * Sets the maximum width the progress bar can have. 444 * @param maxWidth the maximum width to be set, in pixels 445 * @attr ref android.R.styleable#ProgressBar_maxWidth 446 */ setMaxWidth(@x int maxWidth)447 public void setMaxWidth(@Px int maxWidth) { 448 mMaxWidth = maxWidth; 449 requestLayout(); 450 } 451 452 /** 453 * @return the maximum width the progress bar can have, in pixels 454 */ getMaxWidth()455 @Px public int getMaxWidth() { 456 return mMaxWidth; 457 } 458 459 /** 460 * Sets the minimum height the progress bar can have. 461 * @param minHeight the minimum height to be set, in pixels 462 * @attr ref android.R.styleable#ProgressBar_minHeight 463 */ setMinHeight(@x int minHeight)464 public void setMinHeight(@Px int minHeight) { 465 mMinHeight = minHeight; 466 requestLayout(); 467 } 468 469 /** 470 * @return the minimum height the progress bar can have, in pixels 471 */ getMinHeight()472 @Px public int getMinHeight() { 473 return mMinHeight; 474 } 475 476 /** 477 * Sets the maximum height the progress bar can have. 478 * @param maxHeight the maximum height to be set, in pixels 479 * @attr ref android.R.styleable#ProgressBar_maxHeight 480 */ setMaxHeight(@x int maxHeight)481 public void setMaxHeight(@Px int maxHeight) { 482 mMaxHeight = maxHeight; 483 requestLayout(); 484 } 485 486 /** 487 * @return the maximum height the progress bar can have, in pixels 488 */ getMaxHeight()489 @Px public int getMaxHeight() { 490 return mMaxHeight; 491 } 492 493 /** 494 * Returns {@code true} if the target drawable needs to be tileified. 495 * 496 * @param dr the drawable to check 497 * @return {@code true} if the target drawable needs to be tileified, 498 * {@code false} otherwise 499 */ needsTileify(Drawable dr)500 private static boolean needsTileify(Drawable dr) { 501 if (dr instanceof LayerDrawable) { 502 final LayerDrawable orig = (LayerDrawable) dr; 503 final int N = orig.getNumberOfLayers(); 504 for (int i = 0; i < N; i++) { 505 if (needsTileify(orig.getDrawable(i))) { 506 return true; 507 } 508 } 509 return false; 510 } 511 512 if (dr instanceof StateListDrawable) { 513 final StateListDrawable in = (StateListDrawable) dr; 514 final int N = in.getStateCount(); 515 for (int i = 0; i < N; i++) { 516 if (needsTileify(in.getStateDrawable(i))) { 517 return true; 518 } 519 } 520 return false; 521 } 522 523 // If there's a bitmap that's not wrapped with a ClipDrawable or 524 // ScaleDrawable, we'll need to wrap it and apply tiling. 525 if (dr instanceof BitmapDrawable) { 526 return true; 527 } 528 529 return false; 530 } 531 532 /** 533 * Converts a drawable to a tiled version of itself. It will recursively 534 * traverse layer and state list drawables. 535 */ 536 @UnsupportedAppUsage tileify(Drawable drawable, boolean clip)537 private Drawable tileify(Drawable drawable, boolean clip) { 538 // TODO: This is a terrible idea that potentially destroys any drawable 539 // that extends any of these classes. We *really* need to remove this. 540 541 if (drawable instanceof LayerDrawable) { 542 final LayerDrawable orig = (LayerDrawable) drawable; 543 final int N = orig.getNumberOfLayers(); 544 final Drawable[] outDrawables = new Drawable[N]; 545 546 for (int i = 0; i < N; i++) { 547 final int id = orig.getId(i); 548 outDrawables[i] = tileify(orig.getDrawable(i), 549 (id == R.id.progress || id == R.id.secondaryProgress)); 550 } 551 552 final LayerDrawable clone = new LayerDrawable(outDrawables); 553 for (int i = 0; i < N; i++) { 554 clone.setId(i, orig.getId(i)); 555 clone.setLayerGravity(i, orig.getLayerGravity(i)); 556 clone.setLayerWidth(i, orig.getLayerWidth(i)); 557 clone.setLayerHeight(i, orig.getLayerHeight(i)); 558 clone.setLayerInsetLeft(i, orig.getLayerInsetLeft(i)); 559 clone.setLayerInsetRight(i, orig.getLayerInsetRight(i)); 560 clone.setLayerInsetTop(i, orig.getLayerInsetTop(i)); 561 clone.setLayerInsetBottom(i, orig.getLayerInsetBottom(i)); 562 clone.setLayerInsetStart(i, orig.getLayerInsetStart(i)); 563 clone.setLayerInsetEnd(i, orig.getLayerInsetEnd(i)); 564 } 565 566 return clone; 567 } 568 569 if (drawable instanceof StateListDrawable) { 570 final StateListDrawable in = (StateListDrawable) drawable; 571 final StateListDrawable out = new StateListDrawable(); 572 final int N = in.getStateCount(); 573 for (int i = 0; i < N; i++) { 574 out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); 575 } 576 577 return out; 578 } 579 580 if (drawable instanceof BitmapDrawable) { 581 final Drawable.ConstantState cs = drawable.getConstantState(); 582 final BitmapDrawable clone = (BitmapDrawable) cs.newDrawable(getResources()); 583 clone.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); 584 585 if (mSampleWidth <= 0) { 586 mSampleWidth = clone.getIntrinsicWidth(); 587 } 588 589 if (clip) { 590 return new ClipDrawable(clone, Gravity.LEFT, ClipDrawable.HORIZONTAL); 591 } else { 592 return clone; 593 } 594 } 595 596 return drawable; 597 } 598 getDrawableShape()599 Shape getDrawableShape() { 600 final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; 601 return new RoundRectShape(roundedCorners, null, null); 602 } 603 604 /** 605 * Convert a AnimationDrawable for use as a barberpole animation. 606 * Each frame of the animation is wrapped in a ClipDrawable and 607 * given a tiling BitmapShader. 608 */ tileifyIndeterminate(Drawable drawable)609 private Drawable tileifyIndeterminate(Drawable drawable) { 610 if (drawable instanceof AnimationDrawable) { 611 AnimationDrawable background = (AnimationDrawable) drawable; 612 final int N = background.getNumberOfFrames(); 613 AnimationDrawable newBg = new AnimationDrawable(); 614 newBg.setOneShot(background.isOneShot()); 615 616 for (int i = 0; i < N; i++) { 617 Drawable frame = tileify(background.getFrame(i), true); 618 frame.setLevel(10000); 619 newBg.addFrame(frame, background.getDuration(i)); 620 } 621 newBg.setLevel(10000); 622 drawable = newBg; 623 } 624 return drawable; 625 } 626 627 /** 628 * <p> 629 * Initialize the progress bar's default values: 630 * </p> 631 * <ul> 632 * <li>progress = 0</li> 633 * <li>max = 100</li> 634 * <li>animation duration = 4000 ms</li> 635 * <li>indeterminate = false</li> 636 * <li>behavior = repeat</li> 637 * </ul> 638 */ initProgressBar()639 private void initProgressBar() { 640 mMin = 0; 641 mMax = 100; 642 mProgress = 0; 643 mSecondaryProgress = 0; 644 mIndeterminate = false; 645 mOnlyIndeterminate = false; 646 mDuration = 4000; 647 mBehavior = AlphaAnimation.RESTART; 648 mMinWidth = 24; 649 mMaxWidth = 48; 650 mMinHeight = 24; 651 mMaxHeight = 48; 652 } 653 654 /** 655 * <p>Indicate whether this progress bar is in indeterminate mode.</p> 656 * 657 * @return true if the progress bar is in indeterminate mode 658 */ 659 @InspectableProperty 660 @ViewDebug.ExportedProperty(category = "progress") isIndeterminate()661 public synchronized boolean isIndeterminate() { 662 return mIndeterminate; 663 } 664 665 /** 666 * <p>Change the indeterminate mode for this progress bar. In indeterminate 667 * mode, the progress is ignored and the progress bar shows an infinite 668 * animation instead.</p> 669 * 670 * If this progress bar's style only supports indeterminate mode (such as the circular 671 * progress bars), then this will be ignored. 672 * 673 * @param indeterminate true to enable the indeterminate mode 674 */ 675 @android.view.RemotableViewMethod setIndeterminate(boolean indeterminate)676 public synchronized void setIndeterminate(boolean indeterminate) { 677 if ((!mOnlyIndeterminate || !mIndeterminate) && indeterminate != mIndeterminate) { 678 mIndeterminate = indeterminate; 679 680 if (indeterminate) { 681 // swap between indeterminate and regular backgrounds 682 swapCurrentDrawable(mIndeterminateDrawable); 683 startAnimation(); 684 } else { 685 swapCurrentDrawable(mProgressDrawable); 686 stopAnimation(); 687 } 688 } 689 } 690 swapCurrentDrawable(Drawable newDrawable)691 private void swapCurrentDrawable(Drawable newDrawable) { 692 final Drawable oldDrawable = mCurrentDrawable; 693 mCurrentDrawable = newDrawable; 694 695 if (oldDrawable != mCurrentDrawable) { 696 if (oldDrawable != null) { 697 oldDrawable.setVisible(false, false); 698 } 699 if (mCurrentDrawable != null) { 700 mCurrentDrawable.setVisible(getWindowVisibility() == VISIBLE && isShown(), false); 701 } 702 } 703 } 704 705 /** 706 * <p>Get the drawable used to draw the progress bar in 707 * indeterminate mode.</p> 708 * 709 * @return a {@link android.graphics.drawable.Drawable} instance 710 * 711 * @see #setIndeterminateDrawable(android.graphics.drawable.Drawable) 712 * @see #setIndeterminate(boolean) 713 */ 714 @InspectableProperty getIndeterminateDrawable()715 public Drawable getIndeterminateDrawable() { 716 return mIndeterminateDrawable; 717 } 718 719 /** 720 * Define the drawable used to draw the progress bar in indeterminate mode. 721 * 722 * <p>For the Drawable to animate, it must implement {@link Animatable}, or override 723 * {@link Drawable#onLevelChange(int)}. A Drawable that implements Animatable will be animated 724 * via that interface and therefore provides the greatest amount of customization. A Drawable 725 * that only overrides onLevelChange(int) is animated directly by ProgressBar and only the 726 * animation {@link android.R.styleable#ProgressBar_indeterminateDuration duration}, 727 * {@link android.R.styleable#ProgressBar_indeterminateBehavior repeating behavior}, and 728 * {@link #setInterpolator(Interpolator) interpolator} can be modified, and only before the 729 * indeterminate animation begins. 730 * 731 * @param d the new drawable 732 * @attr ref android.R.styleable#ProgressBar_indeterminateDrawable 733 * @see #getIndeterminateDrawable() 734 * @see #setIndeterminate(boolean) 735 */ setIndeterminateDrawable(Drawable d)736 public void setIndeterminateDrawable(Drawable d) { 737 if (mIndeterminateDrawable != d) { 738 if (mIndeterminateDrawable != null) { 739 mIndeterminateDrawable.setCallback(null); 740 unscheduleDrawable(mIndeterminateDrawable); 741 } 742 743 mIndeterminateDrawable = d; 744 745 if (d != null) { 746 d.setCallback(this); 747 d.setLayoutDirection(getLayoutDirection()); 748 if (d.isStateful()) { 749 d.setState(getDrawableState()); 750 } 751 applyIndeterminateTint(); 752 } 753 754 if (mIndeterminate) { 755 swapCurrentDrawable(d); 756 postInvalidate(); 757 } 758 } 759 } 760 761 /** 762 * Applies a tint to the indeterminate drawable. Does not modify the 763 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 764 * <p> 765 * Subsequent calls to {@link #setIndeterminateDrawable(Drawable)} will 766 * automatically mutate the drawable and apply the specified tint and 767 * tint mode using 768 * {@link Drawable#setTintList(ColorStateList)}. 769 * 770 * @param tint the tint to apply, may be {@code null} to clear tint 771 * 772 * @attr ref android.R.styleable#ProgressBar_indeterminateTint 773 * @see #getIndeterminateTintList() 774 * @see Drawable#setTintList(ColorStateList) 775 */ 776 @RemotableViewMethod setIndeterminateTintList(@ullable ColorStateList tint)777 public void setIndeterminateTintList(@Nullable ColorStateList tint) { 778 if (mProgressTintInfo == null) { 779 mProgressTintInfo = new ProgressTintInfo(); 780 } 781 mProgressTintInfo.mIndeterminateTintList = tint; 782 mProgressTintInfo.mHasIndeterminateTint = true; 783 784 applyIndeterminateTint(); 785 } 786 787 /** 788 * @return the tint applied to the indeterminate drawable 789 * @attr ref android.R.styleable#ProgressBar_indeterminateTint 790 * @see #setIndeterminateTintList(ColorStateList) 791 */ 792 @InspectableProperty(name = "indeterminateTint") 793 @Nullable getIndeterminateTintList()794 public ColorStateList getIndeterminateTintList() { 795 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateTintList : null; 796 } 797 798 /** 799 * Specifies the blending mode used to apply the tint specified by 800 * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate 801 * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 802 * 803 * @param tintMode the blending mode used to apply the tint, may be 804 * {@code null} to clear tint 805 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 806 * @see #setIndeterminateTintList(ColorStateList) 807 * @see Drawable#setTintMode(PorterDuff.Mode) 808 * 809 */ setIndeterminateTintMode(@ullable PorterDuff.Mode tintMode)810 public void setIndeterminateTintMode(@Nullable PorterDuff.Mode tintMode) { 811 setIndeterminateTintBlendMode(tintMode != null 812 ? BlendMode.fromValue(tintMode.nativeInt) : null); 813 } 814 815 /** 816 * Specifies the blending mode used to apply the tint specified by 817 * {@link #setIndeterminateTintList(ColorStateList)} to the indeterminate 818 * drawable. The default mode is {@link PorterDuff.Mode#SRC_IN}. 819 * 820 * @param blendMode the blending mode used to apply the tint, may be 821 * {@code null} to clear tint 822 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 823 * @see #setIndeterminateTintList(ColorStateList) 824 * @see Drawable#setTintBlendMode(BlendMode) 825 */ 826 @RemotableViewMethod setIndeterminateTintBlendMode(@ullable BlendMode blendMode)827 public void setIndeterminateTintBlendMode(@Nullable BlendMode blendMode) { 828 if (mProgressTintInfo == null) { 829 mProgressTintInfo = new ProgressTintInfo(); 830 } 831 mProgressTintInfo.mIndeterminateBlendMode = blendMode; 832 mProgressTintInfo.mHasIndeterminateTintMode = true; 833 834 applyIndeterminateTint(); 835 } 836 837 /** 838 * Returns the blending mode used to apply the tint to the indeterminate 839 * drawable, if specified. 840 * 841 * @return the blending mode used to apply the tint to the indeterminate 842 * drawable 843 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 844 * @see #setIndeterminateTintMode(PorterDuff.Mode) 845 */ 846 @InspectableProperty 847 @Nullable getIndeterminateTintMode()848 public PorterDuff.Mode getIndeterminateTintMode() { 849 BlendMode mode = getIndeterminateTintBlendMode(); 850 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 851 } 852 853 /** 854 * Returns the blending mode used to apply the tint to the indeterminate 855 * drawable, if specified. 856 * 857 * @return the blending mode used to apply the tint to the indeterminate 858 * drawable 859 * @attr ref android.R.styleable#ProgressBar_indeterminateTintMode 860 * @see #setIndeterminateTintBlendMode(BlendMode) 861 */ 862 @InspectableProperty(attributeId = R.styleable.ProgressBar_indeterminateTintMode) 863 @Nullable getIndeterminateTintBlendMode()864 public BlendMode getIndeterminateTintBlendMode() { 865 return mProgressTintInfo != null ? mProgressTintInfo.mIndeterminateBlendMode : null; 866 } 867 applyIndeterminateTint()868 private void applyIndeterminateTint() { 869 if (mIndeterminateDrawable != null && mProgressTintInfo != null) { 870 final ProgressTintInfo tintInfo = mProgressTintInfo; 871 if (tintInfo.mHasIndeterminateTint || tintInfo.mHasIndeterminateTintMode) { 872 mIndeterminateDrawable = mIndeterminateDrawable.mutate(); 873 874 if (tintInfo.mHasIndeterminateTint) { 875 mIndeterminateDrawable.setTintList(tintInfo.mIndeterminateTintList); 876 } 877 878 if (tintInfo.mHasIndeterminateTintMode) { 879 mIndeterminateDrawable.setTintBlendMode(tintInfo.mIndeterminateBlendMode); 880 } 881 882 // The drawable (or one of its children) may not have been 883 // stateful before applying the tint, so let's try again. 884 if (mIndeterminateDrawable.isStateful()) { 885 mIndeterminateDrawable.setState(getDrawableState()); 886 } 887 } 888 } 889 } 890 891 /** 892 * Define the tileable drawable used to draw the progress bar in 893 * indeterminate mode. 894 * <p> 895 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 896 * tiled copy will be generated for display as a progress bar. 897 * 898 * @param d the new drawable 899 * @see #getIndeterminateDrawable() 900 * @see #setIndeterminate(boolean) 901 */ setIndeterminateDrawableTiled(Drawable d)902 public void setIndeterminateDrawableTiled(Drawable d) { 903 if (d != null) { 904 d = tileifyIndeterminate(d); 905 } 906 907 setIndeterminateDrawable(d); 908 } 909 910 /** 911 * <p>Get the drawable used to draw the progress bar in 912 * progress mode.</p> 913 * 914 * @return a {@link android.graphics.drawable.Drawable} instance 915 * 916 * @see #setProgressDrawable(android.graphics.drawable.Drawable) 917 * @see #setIndeterminate(boolean) 918 */ 919 @InspectableProperty getProgressDrawable()920 public Drawable getProgressDrawable() { 921 return mProgressDrawable; 922 } 923 924 /** 925 * Define the drawable used to draw the progress bar in progress mode. 926 * 927 * @param d the new drawable 928 * @see #getProgressDrawable() 929 * @see #setIndeterminate(boolean) 930 */ setProgressDrawable(Drawable d)931 public void setProgressDrawable(Drawable d) { 932 if (mProgressDrawable != d) { 933 if (mProgressDrawable != null) { 934 mProgressDrawable.setCallback(null); 935 unscheduleDrawable(mProgressDrawable); 936 } 937 938 mProgressDrawable = d; 939 940 if (d != null) { 941 d.setCallback(this); 942 d.setLayoutDirection(getLayoutDirection()); 943 if (d.isStateful()) { 944 d.setState(getDrawableState()); 945 } 946 947 // Make sure the ProgressBar is always tall enough 948 int drawableHeight = d.getMinimumHeight(); 949 if (mMaxHeight < drawableHeight) { 950 mMaxHeight = drawableHeight; 951 requestLayout(); 952 } 953 954 applyProgressTints(); 955 } 956 957 if (!mIndeterminate) { 958 swapCurrentDrawable(d); 959 postInvalidate(); 960 } 961 962 updateDrawableBounds(getWidth(), getHeight()); 963 updateDrawableState(); 964 965 doRefreshProgress(R.id.progress, mProgress, false, false, false); 966 doRefreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false, false); 967 } 968 } 969 970 /** 971 * @hide 972 */ 973 @InspectableProperty getMirrorForRtl()974 public boolean getMirrorForRtl() { 975 return mMirrorForRtl; 976 } 977 978 /** 979 * Applies the progress tints in order of increasing specificity. 980 */ applyProgressTints()981 private void applyProgressTints() { 982 if (mProgressDrawable != null && mProgressTintInfo != null) { 983 applyPrimaryProgressTint(); 984 applyProgressBackgroundTint(); 985 applySecondaryProgressTint(); 986 } 987 } 988 989 /** 990 * Should only be called if we've already verified that mProgressDrawable 991 * and mProgressTintInfo are non-null. 992 */ applyPrimaryProgressTint()993 private void applyPrimaryProgressTint() { 994 if (mProgressTintInfo.mHasProgressTint 995 || mProgressTintInfo.mHasProgressTintMode) { 996 final Drawable target = getTintTarget(R.id.progress, true); 997 if (target != null) { 998 if (mProgressTintInfo.mHasProgressTint) { 999 target.setTintList(mProgressTintInfo.mProgressTintList); 1000 } 1001 if (mProgressTintInfo.mHasProgressTintMode) { 1002 target.setTintBlendMode(mProgressTintInfo.mProgressBlendMode); 1003 } 1004 1005 // The drawable (or one of its children) may not have been 1006 // stateful before applying the tint, so let's try again. 1007 if (target.isStateful()) { 1008 target.setState(getDrawableState()); 1009 } 1010 } 1011 } 1012 } 1013 1014 /** 1015 * Should only be called if we've already verified that mProgressDrawable 1016 * and mProgressTintInfo are non-null. 1017 */ applyProgressBackgroundTint()1018 private void applyProgressBackgroundTint() { 1019 if (mProgressTintInfo.mHasProgressBackgroundTint 1020 || mProgressTintInfo.mHasProgressBackgroundTintMode) { 1021 final Drawable target = getTintTarget(R.id.background, false); 1022 if (target != null) { 1023 if (mProgressTintInfo.mHasProgressBackgroundTint) { 1024 target.setTintList(mProgressTintInfo.mProgressBackgroundTintList); 1025 } 1026 if (mProgressTintInfo.mHasProgressBackgroundTintMode) { 1027 target.setTintBlendMode(mProgressTintInfo.mProgressBackgroundBlendMode); 1028 } 1029 1030 // The drawable (or one of its children) may not have been 1031 // stateful before applying the tint, so let's try again. 1032 if (target.isStateful()) { 1033 target.setState(getDrawableState()); 1034 } 1035 } 1036 } 1037 } 1038 1039 /** 1040 * Should only be called if we've already verified that mProgressDrawable 1041 * and mProgressTintInfo are non-null. 1042 */ applySecondaryProgressTint()1043 private void applySecondaryProgressTint() { 1044 if (mProgressTintInfo.mHasSecondaryProgressTint 1045 || mProgressTintInfo.mHasSecondaryProgressTintMode) { 1046 final Drawable target = getTintTarget(R.id.secondaryProgress, false); 1047 if (target != null) { 1048 if (mProgressTintInfo.mHasSecondaryProgressTint) { 1049 target.setTintList(mProgressTintInfo.mSecondaryProgressTintList); 1050 } 1051 if (mProgressTintInfo.mHasSecondaryProgressTintMode) { 1052 target.setTintBlendMode(mProgressTintInfo.mSecondaryProgressBlendMode); 1053 } 1054 1055 // The drawable (or one of its children) may not have been 1056 // stateful before applying the tint, so let's try again. 1057 if (target.isStateful()) { 1058 target.setState(getDrawableState()); 1059 } 1060 } 1061 } 1062 } 1063 1064 /** 1065 * Applies a tint to the progress indicator, if one exists, or to the 1066 * entire progress drawable otherwise. Does not modify the current tint 1067 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 1068 * <p> 1069 * The progress indicator should be specified as a layer with 1070 * id {@link android.R.id#progress} in a {@link LayerDrawable} 1071 * used as the progress drawable. 1072 * <p> 1073 * Subsequent calls to {@link #setProgressDrawable(Drawable)} will 1074 * automatically mutate the drawable and apply the specified tint and 1075 * tint mode using 1076 * {@link Drawable#setTintList(ColorStateList)}. 1077 * 1078 * @param tint the tint to apply, may be {@code null} to clear tint 1079 * 1080 * @attr ref android.R.styleable#ProgressBar_progressTint 1081 * @see #getProgressTintList() 1082 * @see Drawable#setTintList(ColorStateList) 1083 */ 1084 @RemotableViewMethod setProgressTintList(@ullable ColorStateList tint)1085 public void setProgressTintList(@Nullable ColorStateList tint) { 1086 if (mProgressTintInfo == null) { 1087 mProgressTintInfo = new ProgressTintInfo(); 1088 } 1089 mProgressTintInfo.mProgressTintList = tint; 1090 mProgressTintInfo.mHasProgressTint = true; 1091 1092 if (mProgressDrawable != null) { 1093 applyPrimaryProgressTint(); 1094 } 1095 } 1096 1097 /** 1098 * Returns the tint applied to the progress drawable, if specified. 1099 * 1100 * @return the tint applied to the progress drawable 1101 * @attr ref android.R.styleable#ProgressBar_progressTint 1102 * @see #setProgressTintList(ColorStateList) 1103 */ 1104 @InspectableProperty(name = "progressTint") 1105 @Nullable getProgressTintList()1106 public ColorStateList getProgressTintList() { 1107 return mProgressTintInfo != null ? mProgressTintInfo.mProgressTintList : null; 1108 } 1109 1110 /** 1111 * Specifies the blending mode used to apply the tint specified by 1112 * {@link #setProgressTintList(ColorStateList)}} to the progress 1113 * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}. 1114 * 1115 * @param tintMode the blending mode used to apply the tint, may be 1116 * {@code null} to clear tint 1117 * @attr ref android.R.styleable#ProgressBar_progressTintMode 1118 * @see #getProgressTintMode() 1119 * @see Drawable#setTintMode(PorterDuff.Mode) 1120 */ setProgressTintMode(@ullable PorterDuff.Mode tintMode)1121 public void setProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 1122 setProgressTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null); 1123 } 1124 1125 /** 1126 * Specifies the blending mode used to apply the tint specified by 1127 * {@link #setProgressTintList(ColorStateList)}} to the progress 1128 * indicator. The default mode is {@link PorterDuff.Mode#SRC_IN}. 1129 * 1130 * @param blendMode the blending mode used to apply the tint, may be 1131 * {@code null} to clear tint 1132 * @attr ref android.R.styleable#ProgressBar_progressTintMode 1133 * @see #getProgressTintMode() 1134 * @see Drawable#setTintBlendMode(BlendMode) 1135 */ 1136 @RemotableViewMethod setProgressTintBlendMode(@ullable BlendMode blendMode)1137 public void setProgressTintBlendMode(@Nullable BlendMode blendMode) { 1138 if (mProgressTintInfo == null) { 1139 mProgressTintInfo = new ProgressTintInfo(); 1140 } 1141 mProgressTintInfo.mProgressBlendMode = blendMode; 1142 mProgressTintInfo.mHasProgressTintMode = true; 1143 1144 if (mProgressDrawable != null) { 1145 applyPrimaryProgressTint(); 1146 } 1147 } 1148 1149 /** 1150 * Returns the blending mode used to apply the tint to the progress 1151 * drawable, if specified. 1152 * 1153 * @return the blending mode used to apply the tint to the progress 1154 * drawable 1155 * @attr ref android.R.styleable#ProgressBar_progressTintMode 1156 * @see #setProgressTintMode(PorterDuff.Mode) 1157 */ 1158 @InspectableProperty 1159 @Nullable getProgressTintMode()1160 public PorterDuff.Mode getProgressTintMode() { 1161 BlendMode mode = getProgressTintBlendMode(); 1162 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 1163 } 1164 1165 /** 1166 * Returns the blending mode used to apply the tint to the progress 1167 * drawable, if specified. 1168 * 1169 * @return the blending mode used to apply the tint to the progress 1170 * drawable 1171 * @attr ref android.R.styleable#ProgressBar_progressTintMode 1172 * @see #setProgressTintBlendMode(BlendMode) 1173 */ 1174 @InspectableProperty(attributeId = android.R.styleable.ProgressBar_progressTintMode) 1175 @Nullable getProgressTintBlendMode()1176 public BlendMode getProgressTintBlendMode() { 1177 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBlendMode : null; 1178 } 1179 1180 /** 1181 * Applies a tint to the progress background, if one exists. Does not 1182 * modify the current tint mode, which is 1183 * {@link PorterDuff.Mode#SRC_ATOP} by default. 1184 * <p> 1185 * The progress background must be specified as a layer with 1186 * id {@link android.R.id#background} in a {@link LayerDrawable} 1187 * used as the progress drawable. 1188 * <p> 1189 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 1190 * drawable contains a progress background will automatically mutate the 1191 * drawable and apply the specified tint and tint mode using 1192 * {@link Drawable#setTintList(ColorStateList)}. 1193 * 1194 * @param tint the tint to apply, may be {@code null} to clear tint 1195 * 1196 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 1197 * @see #getProgressBackgroundTintList() 1198 * @see Drawable#setTintList(ColorStateList) 1199 */ 1200 @RemotableViewMethod setProgressBackgroundTintList(@ullable ColorStateList tint)1201 public void setProgressBackgroundTintList(@Nullable ColorStateList tint) { 1202 if (mProgressTintInfo == null) { 1203 mProgressTintInfo = new ProgressTintInfo(); 1204 } 1205 mProgressTintInfo.mProgressBackgroundTintList = tint; 1206 mProgressTintInfo.mHasProgressBackgroundTint = true; 1207 1208 if (mProgressDrawable != null) { 1209 applyProgressBackgroundTint(); 1210 } 1211 } 1212 1213 /** 1214 * Returns the tint applied to the progress background, if specified. 1215 * 1216 * @return the tint applied to the progress background 1217 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTint 1218 * @see #setProgressBackgroundTintList(ColorStateList) 1219 */ 1220 @InspectableProperty(name = "progressBackgroundTint") 1221 @Nullable getProgressBackgroundTintList()1222 public ColorStateList getProgressBackgroundTintList() { 1223 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundTintList : null; 1224 } 1225 1226 /** 1227 * Specifies the blending mode used to apply the tint specified by 1228 * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress 1229 * background. The default mode is {@link PorterDuff.Mode#SRC_IN}. 1230 * 1231 * @param tintMode the blending mode used to apply the tint, may be 1232 * {@code null} to clear tint 1233 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 1234 * @see #setProgressBackgroundTintList(ColorStateList) 1235 * @see Drawable#setTintMode(PorterDuff.Mode) 1236 */ setProgressBackgroundTintMode(@ullable PorterDuff.Mode tintMode)1237 public void setProgressBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) { 1238 setProgressBackgroundTintBlendMode(tintMode != null 1239 ? BlendMode.fromValue(tintMode.nativeInt) : null); 1240 } 1241 1242 /** 1243 * Specifies the blending mode used to apply the tint specified by 1244 * {@link #setProgressBackgroundTintList(ColorStateList)}} to the progress 1245 * background. The default mode is {@link BlendMode#SRC_IN}. 1246 * 1247 * @param blendMode the blending mode used to apply the tint, may be 1248 * {@code null} to clear tint 1249 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 1250 * @see #setProgressBackgroundTintList(ColorStateList) 1251 * @see Drawable#setTintBlendMode(BlendMode) 1252 */ 1253 @RemotableViewMethod setProgressBackgroundTintBlendMode(@ullable BlendMode blendMode)1254 public void setProgressBackgroundTintBlendMode(@Nullable BlendMode blendMode) { 1255 if (mProgressTintInfo == null) { 1256 mProgressTintInfo = new ProgressTintInfo(); 1257 } 1258 mProgressTintInfo.mProgressBackgroundBlendMode = blendMode; 1259 mProgressTintInfo.mHasProgressBackgroundTintMode = true; 1260 1261 if (mProgressDrawable != null) { 1262 applyProgressBackgroundTint(); 1263 } 1264 } 1265 1266 /** 1267 * @return the blending mode used to apply the tint to the progress 1268 * background 1269 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 1270 * @see #setProgressBackgroundTintMode(PorterDuff.Mode) 1271 */ 1272 @InspectableProperty 1273 @Nullable getProgressBackgroundTintMode()1274 public PorterDuff.Mode getProgressBackgroundTintMode() { 1275 BlendMode mode = getProgressBackgroundTintBlendMode(); 1276 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 1277 } 1278 1279 /** 1280 * @return the blending mode used to apply the tint to the progress 1281 * background 1282 * @attr ref android.R.styleable#ProgressBar_progressBackgroundTintMode 1283 * @see #setProgressBackgroundTintBlendMode(BlendMode) 1284 */ 1285 @InspectableProperty(attributeId = R.styleable.ProgressBar_progressBackgroundTintMode) 1286 @Nullable getProgressBackgroundTintBlendMode()1287 public BlendMode getProgressBackgroundTintBlendMode() { 1288 return mProgressTintInfo != null ? mProgressTintInfo.mProgressBackgroundBlendMode : null; 1289 } 1290 1291 /** 1292 * Applies a tint to the secondary progress indicator, if one exists. 1293 * Does not modify the current tint mode, which is 1294 * {@link PorterDuff.Mode#SRC_ATOP} by default. 1295 * <p> 1296 * The secondary progress indicator must be specified as a layer with 1297 * id {@link android.R.id#secondaryProgress} in a {@link LayerDrawable} 1298 * used as the progress drawable. 1299 * <p> 1300 * Subsequent calls to {@link #setProgressDrawable(Drawable)} where the 1301 * drawable contains a secondary progress indicator will automatically 1302 * mutate the drawable and apply the specified tint and tint mode using 1303 * {@link Drawable#setTintList(ColorStateList)}. 1304 * 1305 * @param tint the tint to apply, may be {@code null} to clear tint 1306 * 1307 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1308 * @see #getSecondaryProgressTintList() 1309 * @see Drawable#setTintList(ColorStateList) 1310 */ 1311 @RemotableViewMethod setSecondaryProgressTintList(@ullable ColorStateList tint)1312 public void setSecondaryProgressTintList(@Nullable ColorStateList tint) { 1313 if (mProgressTintInfo == null) { 1314 mProgressTintInfo = new ProgressTintInfo(); 1315 } 1316 mProgressTintInfo.mSecondaryProgressTintList = tint; 1317 mProgressTintInfo.mHasSecondaryProgressTint = true; 1318 1319 if (mProgressDrawable != null) { 1320 applySecondaryProgressTint(); 1321 } 1322 } 1323 1324 /** 1325 * Returns the tint applied to the secondary progress drawable, if 1326 * specified. 1327 * 1328 * @return the tint applied to the secondary progress drawable 1329 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTint 1330 * @see #setSecondaryProgressTintList(ColorStateList) 1331 */ 1332 @InspectableProperty(name = "secondaryProgressTint") 1333 @Nullable getSecondaryProgressTintList()1334 public ColorStateList getSecondaryProgressTintList() { 1335 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressTintList : null; 1336 } 1337 1338 /** 1339 * Specifies the blending mode used to apply the tint specified by 1340 * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary 1341 * progress indicator. The default mode is 1342 * {@link PorterDuff.Mode#SRC_ATOP}. 1343 * 1344 * @param tintMode the blending mode used to apply the tint, may be 1345 * {@code null} to clear tint 1346 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1347 * @see #setSecondaryProgressTintList(ColorStateList) 1348 * @see Drawable#setTintMode(PorterDuff.Mode) 1349 */ setSecondaryProgressTintMode(@ullable PorterDuff.Mode tintMode)1350 public void setSecondaryProgressTintMode(@Nullable PorterDuff.Mode tintMode) { 1351 setSecondaryProgressTintBlendMode(tintMode != null 1352 ? BlendMode.fromValue(tintMode.nativeInt) : null); 1353 } 1354 1355 /** 1356 * Specifies the blending mode used to apply the tint specified by 1357 * {@link #setSecondaryProgressTintList(ColorStateList)}} to the secondary 1358 * progress indicator. The default mode is 1359 * {@link PorterDuff.Mode#SRC_ATOP}. 1360 * 1361 * @param blendMode the blending mode used to apply the tint, may be 1362 * {@code null} to clear tint 1363 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1364 * @see #setSecondaryProgressTintList(ColorStateList) 1365 * @see Drawable#setTintBlendMode(BlendMode) 1366 */ 1367 @RemotableViewMethod setSecondaryProgressTintBlendMode(@ullable BlendMode blendMode)1368 public void setSecondaryProgressTintBlendMode(@Nullable BlendMode blendMode) { 1369 if (mProgressTintInfo == null) { 1370 mProgressTintInfo = new ProgressTintInfo(); 1371 } 1372 mProgressTintInfo.mSecondaryProgressBlendMode = blendMode; 1373 mProgressTintInfo.mHasSecondaryProgressTintMode = true; 1374 1375 if (mProgressDrawable != null) { 1376 applySecondaryProgressTint(); 1377 } 1378 } 1379 1380 /** 1381 * Returns the blending mode used to apply the tint to the secondary 1382 * progress drawable, if specified. 1383 * 1384 * @return the blending mode used to apply the tint to the secondary 1385 * progress drawable 1386 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1387 * @see #setSecondaryProgressTintMode(PorterDuff.Mode) 1388 */ 1389 @InspectableProperty 1390 @Nullable getSecondaryProgressTintMode()1391 public PorterDuff.Mode getSecondaryProgressTintMode() { 1392 BlendMode mode = getSecondaryProgressTintBlendMode(); 1393 return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null; 1394 } 1395 1396 /** 1397 * Returns the blending mode used to apply the tint to the secondary 1398 * progress drawable, if specified. 1399 * 1400 * @return the blending mode used to apply the tint to the secondary 1401 * progress drawable 1402 * @attr ref android.R.styleable#ProgressBar_secondaryProgressTintMode 1403 * @see #setSecondaryProgressTintBlendMode(BlendMode) 1404 */ 1405 @InspectableProperty(attributeId = android.R.styleable.ProgressBar_secondaryProgressTintMode) 1406 @Nullable getSecondaryProgressTintBlendMode()1407 public BlendMode getSecondaryProgressTintBlendMode() { 1408 return mProgressTintInfo != null ? mProgressTintInfo.mSecondaryProgressBlendMode : null; 1409 } 1410 1411 /** 1412 * Returns the drawable to which a tint or tint mode should be applied. 1413 * 1414 * @param layerId id of the layer to modify 1415 * @param shouldFallback whether the base drawable should be returned 1416 * if the id does not exist 1417 * @return the drawable to modify 1418 */ 1419 @Nullable getTintTarget(int layerId, boolean shouldFallback)1420 private Drawable getTintTarget(int layerId, boolean shouldFallback) { 1421 Drawable layer = null; 1422 1423 final Drawable d = mProgressDrawable; 1424 if (d != null) { 1425 mProgressDrawable = d.mutate(); 1426 1427 if (d instanceof LayerDrawable) { 1428 layer = ((LayerDrawable) d).findDrawableByLayerId(layerId); 1429 } 1430 1431 if (shouldFallback && layer == null) { 1432 layer = d; 1433 } 1434 } 1435 1436 return layer; 1437 } 1438 1439 /** 1440 * Define the tileable drawable used to draw the progress bar in 1441 * progress mode. 1442 * <p> 1443 * If the drawable is a BitmapDrawable or contains BitmapDrawables, a 1444 * tiled copy will be generated for display as a progress bar. 1445 * 1446 * @param d the new drawable 1447 * @see #getProgressDrawable() 1448 * @see #setIndeterminate(boolean) 1449 */ setProgressDrawableTiled(Drawable d)1450 public void setProgressDrawableTiled(Drawable d) { 1451 if (d != null) { 1452 d = tileify(d, false); 1453 } 1454 1455 setProgressDrawable(d); 1456 } 1457 1458 /** 1459 * Returns the drawable currently used to draw the progress bar. This will be 1460 * either {@link #getProgressDrawable()} or {@link #getIndeterminateDrawable()} 1461 * depending on whether the progress bar is in determinate or indeterminate mode. 1462 * 1463 * @return the drawable currently used to draw the progress bar 1464 */ 1465 @Nullable getCurrentDrawable()1466 public Drawable getCurrentDrawable() { 1467 return mCurrentDrawable; 1468 } 1469 1470 @Override verifyDrawable(@onNull Drawable who)1471 protected boolean verifyDrawable(@NonNull Drawable who) { 1472 return who == mProgressDrawable || who == mIndeterminateDrawable 1473 || super.verifyDrawable(who); 1474 } 1475 1476 @Override jumpDrawablesToCurrentState()1477 public void jumpDrawablesToCurrentState() { 1478 super.jumpDrawablesToCurrentState(); 1479 if (mProgressDrawable != null) mProgressDrawable.jumpToCurrentState(); 1480 if (mIndeterminateDrawable != null) mIndeterminateDrawable.jumpToCurrentState(); 1481 } 1482 1483 /** 1484 * @hide 1485 */ 1486 @Override onResolveDrawables(int layoutDirection)1487 public void onResolveDrawables(int layoutDirection) { 1488 final Drawable d = mCurrentDrawable; 1489 if (d != null) { 1490 d.setLayoutDirection(layoutDirection); 1491 } 1492 if (mIndeterminateDrawable != null) { 1493 mIndeterminateDrawable.setLayoutDirection(layoutDirection); 1494 } 1495 if (mProgressDrawable != null) { 1496 mProgressDrawable.setLayoutDirection(layoutDirection); 1497 } 1498 } 1499 1500 @Override postInvalidate()1501 public void postInvalidate() { 1502 if (!mNoInvalidate) { 1503 super.postInvalidate(); 1504 } 1505 } 1506 1507 private class RefreshProgressRunnable implements Runnable { run()1508 public void run() { 1509 synchronized (ProgressBar.this) { 1510 final int count = mRefreshData.size(); 1511 for (int i = 0; i < count; i++) { 1512 final RefreshData rd = mRefreshData.get(i); 1513 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); 1514 rd.recycle(); 1515 } 1516 mRefreshData.clear(); 1517 mRefreshIsPosted = false; 1518 } 1519 } 1520 } 1521 1522 private static class RefreshData { 1523 private static final int POOL_MAX = 24; 1524 private static final SynchronizedPool<RefreshData> sPool = 1525 new SynchronizedPool<RefreshData>(POOL_MAX); 1526 1527 public int id; 1528 public int progress; 1529 public boolean fromUser; 1530 public boolean animate; 1531 obtain(int id, int progress, boolean fromUser, boolean animate)1532 public static RefreshData obtain(int id, int progress, boolean fromUser, boolean animate) { 1533 RefreshData rd = sPool.acquire(); 1534 if (rd == null) { 1535 rd = new RefreshData(); 1536 } 1537 rd.id = id; 1538 rd.progress = progress; 1539 rd.fromUser = fromUser; 1540 rd.animate = animate; 1541 return rd; 1542 } 1543 recycle()1544 public void recycle() { 1545 sPool.release(this); 1546 } 1547 } 1548 doRefreshProgress(int id, int progress, boolean fromUser, boolean callBackToApp, boolean animate)1549 private synchronized void doRefreshProgress(int id, int progress, boolean fromUser, 1550 boolean callBackToApp, boolean animate) { 1551 int range = mMax - mMin; 1552 final float scale = range > 0 ? (progress - mMin) / (float) range : 0; 1553 final boolean isPrimary = id == R.id.progress; 1554 1555 if (isPrimary && animate) { 1556 final ObjectAnimator animator = ObjectAnimator.ofFloat(this, VISUAL_PROGRESS, scale); 1557 animator.setAutoCancel(true); 1558 animator.setDuration(PROGRESS_ANIM_DURATION); 1559 animator.setInterpolator(PROGRESS_ANIM_INTERPOLATOR); 1560 animator.addListener(new AnimatorListenerAdapter() { 1561 @Override 1562 public void onAnimationEnd(Animator animation) { 1563 mLastProgressAnimator = null; 1564 } 1565 }); 1566 animator.start(); 1567 mLastProgressAnimator = animator; 1568 } else { 1569 if (isPrimary && mLastProgressAnimator != null) { 1570 mLastProgressAnimator.cancel(); 1571 mLastProgressAnimator = null; 1572 } 1573 setVisualProgress(id, scale); 1574 } 1575 1576 if (isPrimary && callBackToApp) { 1577 onProgressRefresh(scale, fromUser, progress); 1578 } 1579 } 1580 getPercent(int progress)1581 private float getPercent(int progress) { 1582 final float maxProgress = getMax(); 1583 final float minProgress = getMin(); 1584 final float currentProgress = progress; 1585 final float diffProgress = maxProgress - minProgress; 1586 if (diffProgress <= 0.0f) { 1587 return 0.0f; 1588 } 1589 final float percent = (currentProgress - minProgress) / diffProgress; 1590 return Math.max(0.0f, Math.min(1.0f, percent)); 1591 } 1592 1593 /** 1594 * Default percentage format of the state description based on progress, for example, 1595 * "50 percent". 1596 * 1597 * @param progress the progress value, between {@link #getMin()} and {@link #getMax()} 1598 * @return state description based on progress 1599 */ formatStateDescription(int progress)1600 private CharSequence formatStateDescription(int progress) { 1601 // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed 1602 // non-null, so the first time this is called we will always get the appropriate 1603 // NumberFormat, then never regenerate it unless the locale changes on the fly. 1604 final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); 1605 if (!curLocale.equals(mCachedLocale)) { 1606 mCachedLocale = curLocale; 1607 mPercentFormat = NumberFormat.getPercentInstance(curLocale); 1608 } 1609 return mPercentFormat.format(getPercent(progress)); 1610 } 1611 1612 /** 1613 * This function is called when an instance or subclass sets the state description. Once this 1614 * is called and the argument is not null, the app developer will be responsible for updating 1615 * state description when progress changes and the default state description will not be used. 1616 * App developers can restore the default behavior by setting the argument to null. If set 1617 * progress is called first and then setStateDescription is called, two state change events 1618 * will be merged by event throttling and we can still get the correct state description. 1619 * 1620 * @param stateDescription The state description. 1621 */ 1622 @Override 1623 @RemotableViewMethod setStateDescription(@ullable CharSequence stateDescription)1624 public void setStateDescription(@Nullable CharSequence stateDescription) { 1625 mCustomStateDescription = stateDescription; 1626 if (stateDescription == null) { 1627 super.setStateDescription(formatStateDescription(mProgress)); 1628 } else { 1629 super.setStateDescription(stateDescription); 1630 } 1631 } 1632 onProgressRefresh(float scale, boolean fromUser, int progress)1633 void onProgressRefresh(float scale, boolean fromUser, int progress) { 1634 if (AccessibilityManager.getInstance(mContext).isEnabled() 1635 && mCustomStateDescription == null) { 1636 super.setStateDescription(formatStateDescription(mProgress)); 1637 } 1638 } 1639 1640 /** 1641 * Sets the visual state of a progress indicator. 1642 * 1643 * @param id the identifier of the progress indicator 1644 * @param progress the visual progress in the range [0...1] 1645 */ setVisualProgress(int id, float progress)1646 private void setVisualProgress(int id, float progress) { 1647 mVisualProgress = progress; 1648 1649 Drawable d = mCurrentDrawable; 1650 1651 if (d instanceof LayerDrawable) { 1652 d = ((LayerDrawable) d).findDrawableByLayerId(id); 1653 if (d == null) { 1654 // If we can't find the requested layer, fall back to setting 1655 // the level of the entire drawable. This will break if 1656 // progress is set on multiple elements, but the theme-default 1657 // drawable will always have all layer IDs present. 1658 d = mCurrentDrawable; 1659 } 1660 } 1661 1662 if (d != null) { 1663 final int level = (int) (progress * MAX_LEVEL); 1664 d.setLevel(level); 1665 } else { 1666 invalidate(); 1667 } 1668 1669 onVisualProgressChanged(id, progress); 1670 } 1671 1672 /** 1673 * Called when the visual state of a progress indicator changes. 1674 * 1675 * @param id the identifier of the progress indicator 1676 * @param progress the visual progress in the range [0...1] 1677 */ onVisualProgressChanged(int id, float progress)1678 void onVisualProgressChanged(int id, float progress) { 1679 // Stub method. 1680 } 1681 1682 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) refreshProgress(int id, int progress, boolean fromUser, boolean animate)1683 private synchronized void refreshProgress(int id, int progress, boolean fromUser, 1684 boolean animate) { 1685 if (mUiThreadId == Thread.currentThread().getId()) { 1686 doRefreshProgress(id, progress, fromUser, true, animate); 1687 } else { 1688 if (mRefreshProgressRunnable == null) { 1689 mRefreshProgressRunnable = new RefreshProgressRunnable(); 1690 } 1691 1692 final RefreshData rd = RefreshData.obtain(id, progress, fromUser, animate); 1693 mRefreshData.add(rd); 1694 if (mAttached && !mRefreshIsPosted) { 1695 post(mRefreshProgressRunnable); 1696 mRefreshIsPosted = true; 1697 } 1698 } 1699 } 1700 1701 /** 1702 * Sets the current progress to the specified value. Does not do anything 1703 * if the progress bar is in indeterminate mode. 1704 * <p> 1705 * This method will immediately update the visual position of the progress 1706 * indicator. To animate the visual position to the target value, use 1707 * {@link #setProgress(int, boolean)}}. 1708 * 1709 * @param progress the new progress, between {@link #getMin()} and {@link #getMax()} 1710 * 1711 * @see #setIndeterminate(boolean) 1712 * @see #isIndeterminate() 1713 * @see #getProgress() 1714 * @see #incrementProgressBy(int) 1715 */ 1716 @android.view.RemotableViewMethod setProgress(int progress)1717 public synchronized void setProgress(int progress) { 1718 setProgressInternal(progress, false, false); 1719 } 1720 1721 /** 1722 * Sets the current progress to the specified value, optionally animating 1723 * the visual position between the current and target values. 1724 * <p> 1725 * Animation does not affect the result of {@link #getProgress()}, which 1726 * will return the target value immediately after this method is called. 1727 * 1728 * @param progress the new progress value, between {@link #getMin()} and {@link #getMax()} 1729 * @param animate {@code true} to animate between the current and target 1730 * values or {@code false} to not animate 1731 */ setProgress(int progress, boolean animate)1732 public void setProgress(int progress, boolean animate) { 1733 setProgressInternal(progress, false, animate); 1734 } 1735 1736 @android.view.RemotableViewMethod 1737 @UnsupportedAppUsage setProgressInternal(int progress, boolean fromUser, boolean animate)1738 synchronized boolean setProgressInternal(int progress, boolean fromUser, boolean animate) { 1739 if (mIndeterminate) { 1740 // Not applicable. 1741 return false; 1742 } 1743 1744 progress = MathUtils.constrain(progress, mMin, mMax); 1745 1746 if (progress == mProgress) { 1747 // No change from current. 1748 return false; 1749 } 1750 1751 mProgress = progress; 1752 refreshProgress(R.id.progress, mProgress, fromUser, animate); 1753 return true; 1754 } 1755 1756 /** 1757 * <p> 1758 * Set the current secondary progress to the specified value. Does not do 1759 * anything if the progress bar is in indeterminate mode. 1760 * </p> 1761 * 1762 * @param secondaryProgress the new secondary progress, between {@link #getMin()} and 1763 * {@link #getMax()} 1764 * @see #setIndeterminate(boolean) 1765 * @see #isIndeterminate() 1766 * @see #getSecondaryProgress() 1767 * @see #incrementSecondaryProgressBy(int) 1768 */ 1769 @android.view.RemotableViewMethod setSecondaryProgress(int secondaryProgress)1770 public synchronized void setSecondaryProgress(int secondaryProgress) { 1771 if (mIndeterminate) { 1772 return; 1773 } 1774 1775 if (secondaryProgress < mMin) { 1776 secondaryProgress = mMin; 1777 } 1778 1779 if (secondaryProgress > mMax) { 1780 secondaryProgress = mMax; 1781 } 1782 1783 if (secondaryProgress != mSecondaryProgress) { 1784 mSecondaryProgress = secondaryProgress; 1785 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 1786 } 1787 } 1788 1789 /** 1790 * <p>Get the progress bar's current level of progress. Return 0 when the 1791 * progress bar is in indeterminate mode.</p> 1792 * 1793 * @return the current progress, between {@link #getMin()} and {@link #getMax()} 1794 * 1795 * @see #setIndeterminate(boolean) 1796 * @see #isIndeterminate() 1797 * @see #setProgress(int) 1798 * @see #setMax(int) 1799 * @see #getMax() 1800 */ 1801 @ViewDebug.ExportedProperty(category = "progress") 1802 @InspectableProperty getProgress()1803 public synchronized int getProgress() { 1804 return mIndeterminate ? 0 : mProgress; 1805 } 1806 1807 /** 1808 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 1809 * progress bar is in indeterminate mode.</p> 1810 * 1811 * @return the current secondary progress, between {@link #getMin()} and {@link #getMax()} 1812 * 1813 * @see #setIndeterminate(boolean) 1814 * @see #isIndeterminate() 1815 * @see #setSecondaryProgress(int) 1816 * @see #setMax(int) 1817 * @see #getMax() 1818 */ 1819 @ViewDebug.ExportedProperty(category = "progress") 1820 @InspectableProperty getSecondaryProgress()1821 public synchronized int getSecondaryProgress() { 1822 return mIndeterminate ? 0 : mSecondaryProgress; 1823 } 1824 1825 /** 1826 * <p>Return the lower limit of this progress bar's range.</p> 1827 * 1828 * @return a positive integer 1829 * 1830 * @see #setMin(int) 1831 * @see #getProgress() 1832 * @see #getSecondaryProgress() 1833 */ 1834 @ViewDebug.ExportedProperty(category = "progress") 1835 @InspectableProperty getMin()1836 public synchronized int getMin() { 1837 return mMin; 1838 } 1839 1840 /** 1841 * <p>Return the upper limit of this progress bar's range.</p> 1842 * 1843 * @return a positive integer 1844 * 1845 * @see #setMax(int) 1846 * @see #getProgress() 1847 * @see #getSecondaryProgress() 1848 */ 1849 @ViewDebug.ExportedProperty(category = "progress") 1850 @InspectableProperty getMax()1851 public synchronized int getMax() { 1852 return mMax; 1853 } 1854 1855 /** 1856 * <p>Set the lower range of the progress bar to <tt>min</tt>.</p> 1857 * 1858 * @param min the lower range of this progress bar 1859 * 1860 * @see #getMin() 1861 * @see #setProgress(int) 1862 * @see #setSecondaryProgress(int) 1863 */ 1864 @android.view.RemotableViewMethod setMin(int min)1865 public synchronized void setMin(int min) { 1866 if (mMaxInitialized) { 1867 if (min > mMax) { 1868 min = mMax; 1869 } 1870 } 1871 mMinInitialized = true; 1872 if (mMaxInitialized && min != mMin) { 1873 mMin = min; 1874 postInvalidate(); 1875 1876 if (mProgress < min) { 1877 mProgress = min; 1878 } 1879 refreshProgress(R.id.progress, mProgress, false, false); 1880 } else { 1881 mMin = min; 1882 } 1883 } 1884 1885 /** 1886 * <p>Set the upper range of the progress bar <tt>max</tt>.</p> 1887 * 1888 * @param max the upper range of this progress bar 1889 * 1890 * @see #getMax() 1891 * @see #setProgress(int) 1892 * @see #setSecondaryProgress(int) 1893 */ 1894 @android.view.RemotableViewMethod setMax(int max)1895 public synchronized void setMax(int max) { 1896 if (mMinInitialized) { 1897 if (max < mMin) { 1898 max = mMin; 1899 } 1900 } 1901 mMaxInitialized = true; 1902 if (mMinInitialized && max != mMax) { 1903 mMax = max; 1904 postInvalidate(); 1905 1906 if (mProgress > max) { 1907 mProgress = max; 1908 } 1909 refreshProgress(R.id.progress, mProgress, false, false); 1910 } else { 1911 mMax = max; 1912 } 1913 } 1914 1915 /** 1916 * <p>Increase the progress bar's progress by the specified amount.</p> 1917 * 1918 * @param diff the amount by which the progress must be increased 1919 * 1920 * @see #setProgress(int) 1921 */ incrementProgressBy(int diff)1922 public synchronized final void incrementProgressBy(int diff) { 1923 setProgress(mProgress + diff); 1924 } 1925 1926 /** 1927 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 1928 * 1929 * @param diff the amount by which the secondary progress must be increased 1930 * 1931 * @see #setSecondaryProgress(int) 1932 */ incrementSecondaryProgressBy(int diff)1933 public synchronized final void incrementSecondaryProgressBy(int diff) { 1934 setSecondaryProgress(mSecondaryProgress + diff); 1935 } 1936 1937 /** 1938 * <p>Start the indeterminate progress animation.</p> 1939 */ 1940 @UnsupportedAppUsage startAnimation()1941 void startAnimation() { 1942 if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) { 1943 return; 1944 } 1945 1946 if (mIndeterminateDrawable instanceof Animatable) { 1947 mShouldStartAnimationDrawable = true; 1948 mHasAnimation = false; 1949 } else { 1950 mHasAnimation = true; 1951 1952 if (mInterpolator == null) { 1953 mInterpolator = new LinearInterpolator(); 1954 } 1955 1956 if (mTransformation == null) { 1957 mTransformation = new Transformation(); 1958 } else { 1959 mTransformation.clear(); 1960 } 1961 1962 if (mAnimation == null) { 1963 mAnimation = new AlphaAnimation(0.0f, 1.0f); 1964 } else { 1965 mAnimation.reset(); 1966 } 1967 1968 mAnimation.setRepeatMode(mBehavior); 1969 mAnimation.setRepeatCount(Animation.INFINITE); 1970 mAnimation.setDuration(mDuration); 1971 mAnimation.setInterpolator(mInterpolator); 1972 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 1973 } 1974 postInvalidate(); 1975 } 1976 1977 /** 1978 * <p>Stop the indeterminate progress animation.</p> 1979 */ 1980 @UnsupportedAppUsage stopAnimation()1981 void stopAnimation() { 1982 mHasAnimation = false; 1983 if (mIndeterminateDrawable instanceof Animatable) { 1984 ((Animatable) mIndeterminateDrawable).stop(); 1985 mShouldStartAnimationDrawable = false; 1986 } 1987 postInvalidate(); 1988 } 1989 1990 /** 1991 * Sets the acceleration curve for the indeterminate animation. 1992 * 1993 * <p>The interpolator is loaded as a resource from the specified context. Defaults to a linear 1994 * interpolation. 1995 * 1996 * <p>The interpolator only affects the indeterminate animation if the 1997 * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not 1998 * implement {@link Animatable}. 1999 * 2000 * <p>This call must be made before the indeterminate animation starts for it to have an affect. 2001 * 2002 * @param context The application environment 2003 * @param resID The resource identifier of the interpolator to load 2004 * @attr ref android.R.styleable#ProgressBar_interpolator 2005 * @see #setInterpolator(Interpolator) 2006 * @see #getInterpolator() 2007 */ setInterpolator(Context context, @InterpolatorRes int resID)2008 public void setInterpolator(Context context, @InterpolatorRes int resID) { 2009 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 2010 } 2011 2012 /** 2013 * Sets the acceleration curve for the indeterminate animation. 2014 * Defaults to a linear interpolation. 2015 * 2016 * <p>The interpolator only affects the indeterminate animation if the 2017 * {@link #setIndeterminateDrawable(Drawable) supplied indeterminate drawable} does not 2018 * implement {@link Animatable}. 2019 * 2020 * <p>This call must be made before the indeterminate animation starts for it to have 2021 * an affect. 2022 * 2023 * @param interpolator The interpolator which defines the acceleration curve 2024 * @attr ref android.R.styleable#ProgressBar_interpolator 2025 * @see #setInterpolator(Context, int) 2026 * @see #getInterpolator() 2027 */ setInterpolator(Interpolator interpolator)2028 public void setInterpolator(Interpolator interpolator) { 2029 mInterpolator = interpolator; 2030 } 2031 2032 /** 2033 * Gets the acceleration curve type for the indeterminate animation. 2034 * 2035 * @return the {@link Interpolator} associated to this animation 2036 * @attr ref android.R.styleable#ProgressBar_interpolator 2037 * @see #setInterpolator(Context, int) 2038 * @see #setInterpolator(Interpolator) 2039 */ 2040 @InspectableProperty getInterpolator()2041 public Interpolator getInterpolator() { 2042 return mInterpolator; 2043 } 2044 2045 @Override onVisibilityAggregated(boolean isVisible)2046 public void onVisibilityAggregated(boolean isVisible) { 2047 super.onVisibilityAggregated(isVisible); 2048 2049 if (isVisible != mAggregatedIsVisible) { 2050 mAggregatedIsVisible = isVisible; 2051 2052 if (mIndeterminate) { 2053 // let's be nice with the UI thread 2054 if (isVisible) { 2055 startAnimation(); 2056 } else { 2057 stopAnimation(); 2058 } 2059 } 2060 2061 if (mCurrentDrawable != null) { 2062 mCurrentDrawable.setVisible(isVisible, false); 2063 } 2064 } 2065 } 2066 2067 @Override invalidateDrawable(@onNull Drawable dr)2068 public void invalidateDrawable(@NonNull Drawable dr) { 2069 if (!mInDrawing) { 2070 if (verifyDrawable(dr)) { 2071 final Rect dirty = dr.getBounds(); 2072 final int scrollX = mScrollX + mPaddingLeft; 2073 final int scrollY = mScrollY + mPaddingTop; 2074 2075 invalidate(dirty.left + scrollX, dirty.top + scrollY, 2076 dirty.right + scrollX, dirty.bottom + scrollY); 2077 } else { 2078 super.invalidateDrawable(dr); 2079 } 2080 } 2081 } 2082 2083 @Override onSizeChanged(int w, int h, int oldw, int oldh)2084 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 2085 updateDrawableBounds(w, h); 2086 } 2087 updateDrawableBounds(int w, int h)2088 private void updateDrawableBounds(int w, int h) { 2089 // onDraw will translate the canvas so we draw starting at 0,0. 2090 // Subtract out padding for the purposes of the calculations below. 2091 w -= mPaddingRight + mPaddingLeft; 2092 h -= mPaddingTop + mPaddingBottom; 2093 2094 int right = w; 2095 int bottom = h; 2096 int top = 0; 2097 int left = 0; 2098 2099 if (mIndeterminateDrawable != null) { 2100 // Aspect ratio logic does not apply to AnimationDrawables 2101 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 2102 // Maintain aspect ratio. Certain kinds of animated drawables 2103 // get very confused otherwise. 2104 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 2105 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 2106 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 2107 final float boundAspect = (float) w / h; 2108 if (intrinsicAspect != boundAspect) { 2109 if (boundAspect > intrinsicAspect) { 2110 // New width is larger. Make it smaller to match height. 2111 final int width = (int) (h * intrinsicAspect); 2112 left = (w - width) / 2; 2113 right = left + width; 2114 } else { 2115 // New height is larger. Make it smaller to match width. 2116 final int height = (int) (w * (1 / intrinsicAspect)); 2117 top = (h - height) / 2; 2118 bottom = top + height; 2119 } 2120 } 2121 } 2122 if (isLayoutRtl() && mMirrorForRtl) { 2123 int tempLeft = left; 2124 left = w - right; 2125 right = w - tempLeft; 2126 } 2127 mIndeterminateDrawable.setBounds(left, top, right, bottom); 2128 } 2129 2130 if (mProgressDrawable != null) { 2131 mProgressDrawable.setBounds(0, 0, right, bottom); 2132 } 2133 } 2134 2135 @Override onDraw(Canvas canvas)2136 protected synchronized void onDraw(Canvas canvas) { 2137 super.onDraw(canvas); 2138 2139 drawTrack(canvas); 2140 } 2141 2142 /** 2143 * Draws the progress bar track. 2144 */ drawTrack(Canvas canvas)2145 void drawTrack(Canvas canvas) { 2146 final Drawable d = mCurrentDrawable; 2147 if (d != null) { 2148 // Translate canvas so a indeterminate circular progress bar with padding 2149 // rotates properly in its animation 2150 final int saveCount = canvas.save(); 2151 2152 if (isLayoutRtl() && mMirrorForRtl) { 2153 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 2154 canvas.scale(-1.0f, 1.0f); 2155 } else { 2156 canvas.translate(mPaddingLeft, mPaddingTop); 2157 } 2158 2159 final long time = getDrawingTime(); 2160 if (mHasAnimation) { 2161 mAnimation.getTransformation(time, mTransformation); 2162 final float scale = mTransformation.getAlpha(); 2163 try { 2164 mInDrawing = true; 2165 d.setLevel((int) (scale * MAX_LEVEL)); 2166 } finally { 2167 mInDrawing = false; 2168 } 2169 postInvalidateOnAnimation(); 2170 } 2171 2172 d.draw(canvas); 2173 canvas.restoreToCount(saveCount); 2174 2175 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 2176 ((Animatable) d).start(); 2177 mShouldStartAnimationDrawable = false; 2178 } 2179 } 2180 } 2181 2182 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)2183 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 2184 int dw = 0; 2185 int dh = 0; 2186 2187 final Drawable d = mCurrentDrawable; 2188 if (d != null) { 2189 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 2190 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 2191 } 2192 2193 updateDrawableState(); 2194 2195 dw += mPaddingLeft + mPaddingRight; 2196 dh += mPaddingTop + mPaddingBottom; 2197 2198 final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); 2199 final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); 2200 setMeasuredDimension(measuredWidth, measuredHeight); 2201 } 2202 2203 @Override drawableStateChanged()2204 protected void drawableStateChanged() { 2205 super.drawableStateChanged(); 2206 updateDrawableState(); 2207 } 2208 updateDrawableState()2209 private void updateDrawableState() { 2210 final int[] state = getDrawableState(); 2211 boolean changed = false; 2212 2213 final Drawable progressDrawable = mProgressDrawable; 2214 if (progressDrawable != null && progressDrawable.isStateful()) { 2215 changed |= progressDrawable.setState(state); 2216 } 2217 2218 final Drawable indeterminateDrawable = mIndeterminateDrawable; 2219 if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) { 2220 changed |= indeterminateDrawable.setState(state); 2221 } 2222 2223 if (changed) { 2224 invalidate(); 2225 } 2226 } 2227 2228 @Override drawableHotspotChanged(float x, float y)2229 public void drawableHotspotChanged(float x, float y) { 2230 super.drawableHotspotChanged(x, y); 2231 2232 if (mProgressDrawable != null) { 2233 mProgressDrawable.setHotspot(x, y); 2234 } 2235 2236 if (mIndeterminateDrawable != null) { 2237 mIndeterminateDrawable.setHotspot(x, y); 2238 } 2239 } 2240 2241 static class SavedState extends BaseSavedState { 2242 int progress; 2243 int secondaryProgress; 2244 2245 /** 2246 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 2247 */ SavedState(Parcelable superState)2248 SavedState(Parcelable superState) { 2249 super(superState); 2250 } 2251 2252 /** 2253 * Constructor called from {@link #CREATOR} 2254 */ SavedState(Parcel in)2255 private SavedState(Parcel in) { 2256 super(in); 2257 progress = in.readInt(); 2258 secondaryProgress = in.readInt(); 2259 } 2260 2261 @Override writeToParcel(Parcel out, int flags)2262 public void writeToParcel(Parcel out, int flags) { 2263 super.writeToParcel(out, flags); 2264 out.writeInt(progress); 2265 out.writeInt(secondaryProgress); 2266 } 2267 2268 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR 2269 = new Parcelable.Creator<SavedState>() { 2270 public SavedState createFromParcel(Parcel in) { 2271 return new SavedState(in); 2272 } 2273 2274 public SavedState[] newArray(int size) { 2275 return new SavedState[size]; 2276 } 2277 }; 2278 } 2279 2280 @Override onSaveInstanceState()2281 public Parcelable onSaveInstanceState() { 2282 // Force our ancestor class to save its state 2283 Parcelable superState = super.onSaveInstanceState(); 2284 SavedState ss = new SavedState(superState); 2285 2286 ss.progress = mProgress; 2287 ss.secondaryProgress = mSecondaryProgress; 2288 2289 return ss; 2290 } 2291 2292 @Override onRestoreInstanceState(Parcelable state)2293 public void onRestoreInstanceState(Parcelable state) { 2294 SavedState ss = (SavedState) state; 2295 super.onRestoreInstanceState(ss.getSuperState()); 2296 2297 setProgress(ss.progress); 2298 setSecondaryProgress(ss.secondaryProgress); 2299 } 2300 2301 @Override onAttachedToWindow()2302 protected void onAttachedToWindow() { 2303 super.onAttachedToWindow(); 2304 if (mIndeterminate) { 2305 startAnimation(); 2306 } 2307 if (mRefreshData != null) { 2308 synchronized (this) { 2309 final int count = mRefreshData.size(); 2310 for (int i = 0; i < count; i++) { 2311 final RefreshData rd = mRefreshData.get(i); 2312 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); 2313 rd.recycle(); 2314 } 2315 mRefreshData.clear(); 2316 } 2317 } 2318 mAttached = true; 2319 } 2320 2321 @Override onDetachedFromWindow()2322 protected void onDetachedFromWindow() { 2323 if (mIndeterminate) { 2324 stopAnimation(); 2325 } 2326 if (mRefreshProgressRunnable != null) { 2327 removeCallbacks(mRefreshProgressRunnable); 2328 mRefreshIsPosted = false; 2329 } 2330 // This should come after stopAnimation(), otherwise an invalidate message remains in the 2331 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 2332 super.onDetachedFromWindow(); 2333 mAttached = false; 2334 } 2335 2336 @Override getAccessibilityClassName()2337 public CharSequence getAccessibilityClassName() { 2338 return ProgressBar.class.getName(); 2339 } 2340 2341 /** @hide */ 2342 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)2343 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 2344 super.onInitializeAccessibilityEventInternal(event); 2345 event.setItemCount(mMax - mMin); 2346 event.setCurrentItemIndex(mProgress); 2347 } 2348 2349 /** @hide */ 2350 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)2351 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 2352 super.onInitializeAccessibilityNodeInfoInternal(info); 2353 2354 if (!isIndeterminate()) { 2355 AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain( 2356 AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(), 2357 getProgress()); 2358 info.setRangeInfo(rangeInfo); 2359 info.setStateDescription(formatStateDescription(mProgress)); 2360 } 2361 } 2362 2363 /** @hide */ 2364 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)2365 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 2366 super.encodeProperties(stream); 2367 2368 stream.addProperty("progress:max", getMax()); 2369 stream.addProperty("progress:progress", getProgress()); 2370 stream.addProperty("progress:secondaryProgress", getSecondaryProgress()); 2371 stream.addProperty("progress:indeterminate", isIndeterminate()); 2372 } 2373 2374 /** 2375 * Returns whether the ProgressBar is animating or not. This is essentially the same 2376 * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible, 2377 * as indeterminate ProgressBars are always animating, and non-indeterminate 2378 * ProgressBars are not animating. 2379 * 2380 * @return true if the ProgressBar is animating, false otherwise. 2381 */ isAnimating()2382 public boolean isAnimating() { 2383 return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown(); 2384 } 2385 2386 private static class ProgressTintInfo { 2387 ColorStateList mIndeterminateTintList; 2388 BlendMode mIndeterminateBlendMode; 2389 boolean mHasIndeterminateTint; 2390 boolean mHasIndeterminateTintMode; 2391 2392 ColorStateList mProgressTintList; 2393 BlendMode mProgressBlendMode; 2394 boolean mHasProgressTint; 2395 boolean mHasProgressTintMode; 2396 2397 ColorStateList mProgressBackgroundTintList; 2398 BlendMode mProgressBackgroundBlendMode; 2399 boolean mHasProgressBackgroundTint; 2400 boolean mHasProgressBackgroundTintMode; 2401 2402 ColorStateList mSecondaryProgressTintList; 2403 BlendMode mSecondaryProgressBlendMode; 2404 boolean mHasSecondaryProgressTint; 2405 boolean mHasSecondaryProgressTintMode; 2406 } 2407 2408 /** 2409 * Property wrapper around the visual state of the {@code progress} functionality 2410 * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does 2411 * not correspond directly to the actual progress -- only the visual state. 2412 */ 2413 private final FloatProperty<ProgressBar> VISUAL_PROGRESS = 2414 new FloatProperty<ProgressBar>("visual_progress") { 2415 @Override 2416 public void setValue(ProgressBar object, float value) { 2417 object.setVisualProgress(R.id.progress, value); 2418 object.mVisualProgress = value; 2419 } 2420 2421 @Override 2422 public Float get(ProgressBar object) { 2423 return object.mVisualProgress; 2424 } 2425 }; 2426 } 2427