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