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