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