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