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