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 {@link #getMin()} 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 {@link #getMin()} 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 {@link #getMin()} and 1429 * {@link #getMax()} 1430 * @see #setIndeterminate(boolean) 1431 * @see #isIndeterminate() 1432 * @see #getSecondaryProgress() 1433 * @see #incrementSecondaryProgressBy(int) 1434 */ 1435 @android.view.RemotableViewMethod setSecondaryProgress(int secondaryProgress)1436 public synchronized void setSecondaryProgress(int secondaryProgress) { 1437 if (mIndeterminate) { 1438 return; 1439 } 1440 1441 if (secondaryProgress < mMin) { 1442 secondaryProgress = mMin; 1443 } 1444 1445 if (secondaryProgress > mMax) { 1446 secondaryProgress = mMax; 1447 } 1448 1449 if (secondaryProgress != mSecondaryProgress) { 1450 mSecondaryProgress = secondaryProgress; 1451 refreshProgress(R.id.secondaryProgress, mSecondaryProgress, false, false); 1452 } 1453 } 1454 1455 /** 1456 * <p>Get the progress bar's current level of progress. Return 0 when the 1457 * progress bar is in indeterminate mode.</p> 1458 * 1459 * @return the current progress, between {@link #getMin()} and {@link #getMax()} 1460 * 1461 * @see #setIndeterminate(boolean) 1462 * @see #isIndeterminate() 1463 * @see #setProgress(int) 1464 * @see #setMax(int) 1465 * @see #getMax() 1466 */ 1467 @ViewDebug.ExportedProperty(category = "progress") getProgress()1468 public synchronized int getProgress() { 1469 return mIndeterminate ? 0 : mProgress; 1470 } 1471 1472 /** 1473 * <p>Get the progress bar's current level of secondary progress. Return 0 when the 1474 * progress bar is in indeterminate mode.</p> 1475 * 1476 * @return the current secondary progress, between {@link #getMin()} and {@link #getMax()} 1477 * 1478 * @see #setIndeterminate(boolean) 1479 * @see #isIndeterminate() 1480 * @see #setSecondaryProgress(int) 1481 * @see #setMax(int) 1482 * @see #getMax() 1483 */ 1484 @ViewDebug.ExportedProperty(category = "progress") getSecondaryProgress()1485 public synchronized int getSecondaryProgress() { 1486 return mIndeterminate ? 0 : mSecondaryProgress; 1487 } 1488 1489 /** 1490 * <p>Return the lower limit of this progress bar's range.</p> 1491 * 1492 * @return a positive integer 1493 * 1494 * @see #setMin(int) 1495 * @see #getProgress() 1496 * @see #getSecondaryProgress() 1497 */ 1498 @ViewDebug.ExportedProperty(category = "progress") getMin()1499 public synchronized int getMin() { 1500 return mMin; 1501 } 1502 1503 /** 1504 * <p>Return the upper limit of this progress bar's range.</p> 1505 * 1506 * @return a positive integer 1507 * 1508 * @see #setMax(int) 1509 * @see #getProgress() 1510 * @see #getSecondaryProgress() 1511 */ 1512 @ViewDebug.ExportedProperty(category = "progress") getMax()1513 public synchronized int getMax() { 1514 return mMax; 1515 } 1516 1517 /** 1518 * <p>Set the lower range of the progress bar to <tt>min</tt>.</p> 1519 * 1520 * @param min the lower range of this progress bar 1521 * 1522 * @see #getMin() 1523 * @see #setProgress(int) 1524 * @see #setSecondaryProgress(int) 1525 */ 1526 @android.view.RemotableViewMethod setMin(int min)1527 public synchronized void setMin(int min) { 1528 if (mMaxInitialized) { 1529 if (min > mMax) { 1530 min = mMax; 1531 } 1532 } 1533 mMinInitialized = true; 1534 if (mMaxInitialized && min != mMin) { 1535 mMin = min; 1536 postInvalidate(); 1537 1538 if (mProgress < min) { 1539 mProgress = min; 1540 } 1541 refreshProgress(R.id.progress, mProgress, false, false); 1542 } else { 1543 mMin = min; 1544 } 1545 } 1546 1547 /** 1548 * <p>Set the upper range of the progress bar <tt>max</tt>.</p> 1549 * 1550 * @param max the upper range of this progress bar 1551 * 1552 * @see #getMax() 1553 * @see #setProgress(int) 1554 * @see #setSecondaryProgress(int) 1555 */ 1556 @android.view.RemotableViewMethod setMax(int max)1557 public synchronized void setMax(int max) { 1558 if (mMinInitialized) { 1559 if (max < mMin) { 1560 max = mMin; 1561 } 1562 } 1563 mMaxInitialized = true; 1564 if (mMinInitialized && max != mMax) { 1565 mMax = max; 1566 postInvalidate(); 1567 1568 if (mProgress > max) { 1569 mProgress = max; 1570 } 1571 refreshProgress(R.id.progress, mProgress, false, false); 1572 } else { 1573 mMax = max; 1574 } 1575 } 1576 1577 /** 1578 * <p>Increase the progress bar's progress by the specified amount.</p> 1579 * 1580 * @param diff the amount by which the progress must be increased 1581 * 1582 * @see #setProgress(int) 1583 */ incrementProgressBy(int diff)1584 public synchronized final void incrementProgressBy(int diff) { 1585 setProgress(mProgress + diff); 1586 } 1587 1588 /** 1589 * <p>Increase the progress bar's secondary progress by the specified amount.</p> 1590 * 1591 * @param diff the amount by which the secondary progress must be increased 1592 * 1593 * @see #setSecondaryProgress(int) 1594 */ incrementSecondaryProgressBy(int diff)1595 public synchronized final void incrementSecondaryProgressBy(int diff) { 1596 setSecondaryProgress(mSecondaryProgress + diff); 1597 } 1598 1599 /** 1600 * <p>Start the indeterminate progress animation.</p> 1601 */ startAnimation()1602 void startAnimation() { 1603 if (getVisibility() != VISIBLE || getWindowVisibility() != VISIBLE) { 1604 return; 1605 } 1606 1607 if (mIndeterminateDrawable instanceof Animatable) { 1608 mShouldStartAnimationDrawable = true; 1609 mHasAnimation = false; 1610 } else { 1611 mHasAnimation = true; 1612 1613 if (mInterpolator == null) { 1614 mInterpolator = new LinearInterpolator(); 1615 } 1616 1617 if (mTransformation == null) { 1618 mTransformation = new Transformation(); 1619 } else { 1620 mTransformation.clear(); 1621 } 1622 1623 if (mAnimation == null) { 1624 mAnimation = new AlphaAnimation(0.0f, 1.0f); 1625 } else { 1626 mAnimation.reset(); 1627 } 1628 1629 mAnimation.setRepeatMode(mBehavior); 1630 mAnimation.setRepeatCount(Animation.INFINITE); 1631 mAnimation.setDuration(mDuration); 1632 mAnimation.setInterpolator(mInterpolator); 1633 mAnimation.setStartTime(Animation.START_ON_FIRST_FRAME); 1634 } 1635 postInvalidate(); 1636 } 1637 1638 /** 1639 * <p>Stop the indeterminate progress animation.</p> 1640 */ stopAnimation()1641 void stopAnimation() { 1642 mHasAnimation = false; 1643 if (mIndeterminateDrawable instanceof Animatable) { 1644 ((Animatable) mIndeterminateDrawable).stop(); 1645 mShouldStartAnimationDrawable = false; 1646 } 1647 postInvalidate(); 1648 } 1649 1650 /** 1651 * Sets the acceleration curve for the indeterminate animation. 1652 * The interpolator is loaded as a resource from the specified context. 1653 * 1654 * @param context The application environment 1655 * @param resID The resource identifier of the interpolator to load 1656 */ setInterpolator(Context context, @InterpolatorRes int resID)1657 public void setInterpolator(Context context, @InterpolatorRes int resID) { 1658 setInterpolator(AnimationUtils.loadInterpolator(context, resID)); 1659 } 1660 1661 /** 1662 * Sets the acceleration curve for the indeterminate animation. 1663 * Defaults to a linear interpolation. 1664 * 1665 * @param interpolator The interpolator which defines the acceleration curve 1666 */ setInterpolator(Interpolator interpolator)1667 public void setInterpolator(Interpolator interpolator) { 1668 mInterpolator = interpolator; 1669 } 1670 1671 /** 1672 * Gets the acceleration curve type for the indeterminate animation. 1673 * 1674 * @return the {@link Interpolator} associated to this animation 1675 */ getInterpolator()1676 public Interpolator getInterpolator() { 1677 return mInterpolator; 1678 } 1679 1680 @Override onVisibilityAggregated(boolean isVisible)1681 public void onVisibilityAggregated(boolean isVisible) { 1682 super.onVisibilityAggregated(isVisible); 1683 1684 if (isVisible != mAggregatedIsVisible) { 1685 mAggregatedIsVisible = isVisible; 1686 1687 if (mIndeterminate) { 1688 // let's be nice with the UI thread 1689 if (isVisible) { 1690 startAnimation(); 1691 } else { 1692 stopAnimation(); 1693 } 1694 } 1695 1696 if (mCurrentDrawable != null) { 1697 mCurrentDrawable.setVisible(isVisible, false); 1698 } 1699 } 1700 } 1701 1702 @Override invalidateDrawable(@onNull Drawable dr)1703 public void invalidateDrawable(@NonNull Drawable dr) { 1704 if (!mInDrawing) { 1705 if (verifyDrawable(dr)) { 1706 final Rect dirty = dr.getBounds(); 1707 final int scrollX = mScrollX + mPaddingLeft; 1708 final int scrollY = mScrollY + mPaddingTop; 1709 1710 invalidate(dirty.left + scrollX, dirty.top + scrollY, 1711 dirty.right + scrollX, dirty.bottom + scrollY); 1712 } else { 1713 super.invalidateDrawable(dr); 1714 } 1715 } 1716 } 1717 1718 @Override onSizeChanged(int w, int h, int oldw, int oldh)1719 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 1720 updateDrawableBounds(w, h); 1721 } 1722 updateDrawableBounds(int w, int h)1723 private void updateDrawableBounds(int w, int h) { 1724 // onDraw will translate the canvas so we draw starting at 0,0. 1725 // Subtract out padding for the purposes of the calculations below. 1726 w -= mPaddingRight + mPaddingLeft; 1727 h -= mPaddingTop + mPaddingBottom; 1728 1729 int right = w; 1730 int bottom = h; 1731 int top = 0; 1732 int left = 0; 1733 1734 if (mIndeterminateDrawable != null) { 1735 // Aspect ratio logic does not apply to AnimationDrawables 1736 if (mOnlyIndeterminate && !(mIndeterminateDrawable instanceof AnimationDrawable)) { 1737 // Maintain aspect ratio. Certain kinds of animated drawables 1738 // get very confused otherwise. 1739 final int intrinsicWidth = mIndeterminateDrawable.getIntrinsicWidth(); 1740 final int intrinsicHeight = mIndeterminateDrawable.getIntrinsicHeight(); 1741 final float intrinsicAspect = (float) intrinsicWidth / intrinsicHeight; 1742 final float boundAspect = (float) w / h; 1743 if (intrinsicAspect != boundAspect) { 1744 if (boundAspect > intrinsicAspect) { 1745 // New width is larger. Make it smaller to match height. 1746 final int width = (int) (h * intrinsicAspect); 1747 left = (w - width) / 2; 1748 right = left + width; 1749 } else { 1750 // New height is larger. Make it smaller to match width. 1751 final int height = (int) (w * (1 / intrinsicAspect)); 1752 top = (h - height) / 2; 1753 bottom = top + height; 1754 } 1755 } 1756 } 1757 if (isLayoutRtl() && mMirrorForRtl) { 1758 int tempLeft = left; 1759 left = w - right; 1760 right = w - tempLeft; 1761 } 1762 mIndeterminateDrawable.setBounds(left, top, right, bottom); 1763 } 1764 1765 if (mProgressDrawable != null) { 1766 mProgressDrawable.setBounds(0, 0, right, bottom); 1767 } 1768 } 1769 1770 @Override onDraw(Canvas canvas)1771 protected synchronized void onDraw(Canvas canvas) { 1772 super.onDraw(canvas); 1773 1774 drawTrack(canvas); 1775 } 1776 1777 /** 1778 * Draws the progress bar track. 1779 */ drawTrack(Canvas canvas)1780 void drawTrack(Canvas canvas) { 1781 final Drawable d = mCurrentDrawable; 1782 if (d != null) { 1783 // Translate canvas so a indeterminate circular progress bar with padding 1784 // rotates properly in its animation 1785 final int saveCount = canvas.save(); 1786 1787 if (isLayoutRtl() && mMirrorForRtl) { 1788 canvas.translate(getWidth() - mPaddingRight, mPaddingTop); 1789 canvas.scale(-1.0f, 1.0f); 1790 } else { 1791 canvas.translate(mPaddingLeft, mPaddingTop); 1792 } 1793 1794 final long time = getDrawingTime(); 1795 if (mHasAnimation) { 1796 mAnimation.getTransformation(time, mTransformation); 1797 final float scale = mTransformation.getAlpha(); 1798 try { 1799 mInDrawing = true; 1800 d.setLevel((int) (scale * MAX_LEVEL)); 1801 } finally { 1802 mInDrawing = false; 1803 } 1804 postInvalidateOnAnimation(); 1805 } 1806 1807 d.draw(canvas); 1808 canvas.restoreToCount(saveCount); 1809 1810 if (mShouldStartAnimationDrawable && d instanceof Animatable) { 1811 ((Animatable) d).start(); 1812 mShouldStartAnimationDrawable = false; 1813 } 1814 } 1815 } 1816 1817 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)1818 protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1819 int dw = 0; 1820 int dh = 0; 1821 1822 final Drawable d = mCurrentDrawable; 1823 if (d != null) { 1824 dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth())); 1825 dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight())); 1826 } 1827 1828 updateDrawableState(); 1829 1830 dw += mPaddingLeft + mPaddingRight; 1831 dh += mPaddingTop + mPaddingBottom; 1832 1833 final int measuredWidth = resolveSizeAndState(dw, widthMeasureSpec, 0); 1834 final int measuredHeight = resolveSizeAndState(dh, heightMeasureSpec, 0); 1835 setMeasuredDimension(measuredWidth, measuredHeight); 1836 } 1837 1838 @Override drawableStateChanged()1839 protected void drawableStateChanged() { 1840 super.drawableStateChanged(); 1841 updateDrawableState(); 1842 } 1843 updateDrawableState()1844 private void updateDrawableState() { 1845 final int[] state = getDrawableState(); 1846 boolean changed = false; 1847 1848 final Drawable progressDrawable = mProgressDrawable; 1849 if (progressDrawable != null && progressDrawable.isStateful()) { 1850 changed |= progressDrawable.setState(state); 1851 } 1852 1853 final Drawable indeterminateDrawable = mIndeterminateDrawable; 1854 if (indeterminateDrawable != null && indeterminateDrawable.isStateful()) { 1855 changed |= indeterminateDrawable.setState(state); 1856 } 1857 1858 if (changed) { 1859 invalidate(); 1860 } 1861 } 1862 1863 @Override drawableHotspotChanged(float x, float y)1864 public void drawableHotspotChanged(float x, float y) { 1865 super.drawableHotspotChanged(x, y); 1866 1867 if (mProgressDrawable != null) { 1868 mProgressDrawable.setHotspot(x, y); 1869 } 1870 1871 if (mIndeterminateDrawable != null) { 1872 mIndeterminateDrawable.setHotspot(x, y); 1873 } 1874 } 1875 1876 static class SavedState extends BaseSavedState { 1877 int progress; 1878 int secondaryProgress; 1879 1880 /** 1881 * Constructor called from {@link ProgressBar#onSaveInstanceState()} 1882 */ SavedState(Parcelable superState)1883 SavedState(Parcelable superState) { 1884 super(superState); 1885 } 1886 1887 /** 1888 * Constructor called from {@link #CREATOR} 1889 */ SavedState(Parcel in)1890 private SavedState(Parcel in) { 1891 super(in); 1892 progress = in.readInt(); 1893 secondaryProgress = in.readInt(); 1894 } 1895 1896 @Override writeToParcel(Parcel out, int flags)1897 public void writeToParcel(Parcel out, int flags) { 1898 super.writeToParcel(out, flags); 1899 out.writeInt(progress); 1900 out.writeInt(secondaryProgress); 1901 } 1902 1903 public static final Parcelable.Creator<SavedState> CREATOR 1904 = new Parcelable.Creator<SavedState>() { 1905 public SavedState createFromParcel(Parcel in) { 1906 return new SavedState(in); 1907 } 1908 1909 public SavedState[] newArray(int size) { 1910 return new SavedState[size]; 1911 } 1912 }; 1913 } 1914 1915 @Override onSaveInstanceState()1916 public Parcelable onSaveInstanceState() { 1917 // Force our ancestor class to save its state 1918 Parcelable superState = super.onSaveInstanceState(); 1919 SavedState ss = new SavedState(superState); 1920 1921 ss.progress = mProgress; 1922 ss.secondaryProgress = mSecondaryProgress; 1923 1924 return ss; 1925 } 1926 1927 @Override onRestoreInstanceState(Parcelable state)1928 public void onRestoreInstanceState(Parcelable state) { 1929 SavedState ss = (SavedState) state; 1930 super.onRestoreInstanceState(ss.getSuperState()); 1931 1932 setProgress(ss.progress); 1933 setSecondaryProgress(ss.secondaryProgress); 1934 } 1935 1936 @Override onAttachedToWindow()1937 protected void onAttachedToWindow() { 1938 super.onAttachedToWindow(); 1939 if (mIndeterminate) { 1940 startAnimation(); 1941 } 1942 if (mRefreshData != null) { 1943 synchronized (this) { 1944 final int count = mRefreshData.size(); 1945 for (int i = 0; i < count; i++) { 1946 final RefreshData rd = mRefreshData.get(i); 1947 doRefreshProgress(rd.id, rd.progress, rd.fromUser, true, rd.animate); 1948 rd.recycle(); 1949 } 1950 mRefreshData.clear(); 1951 } 1952 } 1953 mAttached = true; 1954 } 1955 1956 @Override onDetachedFromWindow()1957 protected void onDetachedFromWindow() { 1958 if (mIndeterminate) { 1959 stopAnimation(); 1960 } 1961 if (mRefreshProgressRunnable != null) { 1962 removeCallbacks(mRefreshProgressRunnable); 1963 mRefreshIsPosted = false; 1964 } 1965 if (mAccessibilityEventSender != null) { 1966 removeCallbacks(mAccessibilityEventSender); 1967 } 1968 // This should come after stopAnimation(), otherwise an invalidate message remains in the 1969 // queue, which can prevent the entire view hierarchy from being GC'ed during a rotation 1970 super.onDetachedFromWindow(); 1971 mAttached = false; 1972 } 1973 1974 @Override getAccessibilityClassName()1975 public CharSequence getAccessibilityClassName() { 1976 return ProgressBar.class.getName(); 1977 } 1978 1979 /** @hide */ 1980 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)1981 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 1982 super.onInitializeAccessibilityEventInternal(event); 1983 event.setItemCount(mMax - mMin); 1984 event.setCurrentItemIndex(mProgress); 1985 } 1986 1987 /** @hide */ 1988 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)1989 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 1990 super.onInitializeAccessibilityNodeInfoInternal(info); 1991 1992 if (!isIndeterminate()) { 1993 AccessibilityNodeInfo.RangeInfo rangeInfo = AccessibilityNodeInfo.RangeInfo.obtain( 1994 AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT, getMin(), getMax(), 1995 getProgress()); 1996 info.setRangeInfo(rangeInfo); 1997 } 1998 } 1999 2000 /** 2001 * Schedule a command for sending an accessibility event. 2002 * </br> 2003 * Note: A command is used to ensure that accessibility events 2004 * are sent at most one in a given time frame to save 2005 * system resources while the progress changes quickly. 2006 */ scheduleAccessibilityEventSender()2007 private void scheduleAccessibilityEventSender() { 2008 if (mAccessibilityEventSender == null) { 2009 mAccessibilityEventSender = new AccessibilityEventSender(); 2010 } else { 2011 removeCallbacks(mAccessibilityEventSender); 2012 } 2013 postDelayed(mAccessibilityEventSender, TIMEOUT_SEND_ACCESSIBILITY_EVENT); 2014 } 2015 2016 /** @hide */ 2017 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)2018 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 2019 super.encodeProperties(stream); 2020 2021 stream.addProperty("progress:max", getMax()); 2022 stream.addProperty("progress:progress", getProgress()); 2023 stream.addProperty("progress:secondaryProgress", getSecondaryProgress()); 2024 stream.addProperty("progress:indeterminate", isIndeterminate()); 2025 } 2026 2027 /** 2028 * Returns whether the ProgressBar is animating or not. This is essentially the same 2029 * as whether the ProgressBar is {@link #isIndeterminate() indeterminate} and visible, 2030 * as indeterminate ProgressBars are always animating, and non-indeterminate 2031 * ProgressBars are not animating. 2032 * 2033 * @return true if the ProgressBar is animating, false otherwise. 2034 */ isAnimating()2035 public boolean isAnimating() { 2036 return isIndeterminate() && getWindowVisibility() == VISIBLE && isShown(); 2037 } 2038 2039 /** 2040 * Command for sending an accessibility event. 2041 */ 2042 private class AccessibilityEventSender implements Runnable { run()2043 public void run() { 2044 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); 2045 } 2046 } 2047 2048 private static class ProgressTintInfo { 2049 ColorStateList mIndeterminateTintList; 2050 PorterDuff.Mode mIndeterminateTintMode; 2051 boolean mHasIndeterminateTint; 2052 boolean mHasIndeterminateTintMode; 2053 2054 ColorStateList mProgressTintList; 2055 PorterDuff.Mode mProgressTintMode; 2056 boolean mHasProgressTint; 2057 boolean mHasProgressTintMode; 2058 2059 ColorStateList mProgressBackgroundTintList; 2060 PorterDuff.Mode mProgressBackgroundTintMode; 2061 boolean mHasProgressBackgroundTint; 2062 boolean mHasProgressBackgroundTintMode; 2063 2064 ColorStateList mSecondaryProgressTintList; 2065 PorterDuff.Mode mSecondaryProgressTintMode; 2066 boolean mHasSecondaryProgressTint; 2067 boolean mHasSecondaryProgressTintMode; 2068 } 2069 2070 /** 2071 * Property wrapper around the visual state of the {@code progress} functionality 2072 * handled by the {@link ProgressBar#setProgress(int, boolean)} method. This does 2073 * not correspond directly to the actual progress -- only the visual state. 2074 */ 2075 private final FloatProperty<ProgressBar> VISUAL_PROGRESS = 2076 new FloatProperty<ProgressBar>("visual_progress") { 2077 @Override 2078 public void setValue(ProgressBar object, float value) { 2079 object.setVisualProgress(R.id.progress, value); 2080 object.mVisualProgress = value; 2081 } 2082 2083 @Override 2084 public Float get(ProgressBar object) { 2085 return object.mVisualProgress; 2086 } 2087 }; 2088 } 2089