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