1/* 2 * Copyright (C) 2014 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 17package android.support.v4.widget; 18 19import android.view.animation.AccelerateDecelerateInterpolator; 20import android.view.animation.Interpolator; 21import android.view.animation.Animation; 22import android.view.animation.LinearInterpolator; 23import android.view.animation.Transformation; 24import android.content.Context; 25import android.content.res.Resources; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.ColorFilter; 29import android.graphics.Paint; 30import android.graphics.Paint.Style; 31import android.graphics.Path; 32import android.graphics.PixelFormat; 33import android.graphics.Rect; 34import android.graphics.RectF; 35import android.graphics.drawable.Drawable; 36import android.graphics.drawable.Animatable; 37import android.support.annotation.IntDef; 38import android.support.annotation.NonNull; 39import android.support.v4.view.animation.FastOutSlowInInterpolator; 40import android.util.DisplayMetrics; 41import android.view.View; 42 43import java.lang.annotation.Retention; 44import java.lang.annotation.RetentionPolicy; 45import java.util.ArrayList; 46 47/** 48 * Fancy progress indicator for Material theme. 49 * 50 * @hide 51 */ 52class MaterialProgressDrawable extends Drawable implements Animatable { 53 private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 54 private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 55 56 private static final float FULL_ROTATION = 1080.0f; 57 @Retention(RetentionPolicy.CLASS) 58 @IntDef({LARGE, DEFAULT}) 59 public @interface ProgressDrawableSize {} 60 // Maps to ProgressBar.Large style 61 static final int LARGE = 0; 62 // Maps to ProgressBar default style 63 static final int DEFAULT = 1; 64 65 // Maps to ProgressBar default style 66 private static final int CIRCLE_DIAMETER = 40; 67 private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 68 private static final float STROKE_WIDTH = 2.5f; 69 70 // Maps to ProgressBar.Large style 71 private static final int CIRCLE_DIAMETER_LARGE = 56; 72 private static final float CENTER_RADIUS_LARGE = 12.5f; 73 private static final float STROKE_WIDTH_LARGE = 3f; 74 75 private final int[] COLORS = new int[] { 76 Color.BLACK 77 }; 78 79 /** 80 * The value in the linear interpolator for animating the drawable at which 81 * the color transition should start 82 */ 83 private static final float COLOR_START_DELAY_OFFSET = 0.75f; 84 private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 85 private static final float START_TRIM_DURATION_OFFSET = 0.5f; 86 87 /** The duration of a single progress spin in milliseconds. */ 88 private static final int ANIMATION_DURATION = 1332; 89 90 /** The number of points in the progress "star". */ 91 private static final float NUM_POINTS = 5f; 92 /** The list of animators operating on this drawable. */ 93 private final ArrayList<Animation> mAnimators = new ArrayList<Animation>(); 94 95 /** The indicator ring, used to manage animation state. */ 96 private final Ring mRing; 97 98 /** Canvas rotation in degrees. */ 99 private float mRotation; 100 101 /** Layout info for the arrowhead in dp */ 102 private static final int ARROW_WIDTH = 10; 103 private static final int ARROW_HEIGHT = 5; 104 private static final float ARROW_OFFSET_ANGLE = 5; 105 106 /** Layout info for the arrowhead for the large spinner in dp */ 107 private static final int ARROW_WIDTH_LARGE = 12; 108 private static final int ARROW_HEIGHT_LARGE = 6; 109 private static final float MAX_PROGRESS_ARC = .8f; 110 111 private Resources mResources; 112 private View mParent; 113 private Animation mAnimation; 114 private float mRotationCount; 115 private double mWidth; 116 private double mHeight; 117 boolean mFinishing; 118 119 public MaterialProgressDrawable(Context context, View parent) { 120 mParent = parent; 121 mResources = context.getResources(); 122 123 mRing = new Ring(mCallback); 124 mRing.setColors(COLORS); 125 126 updateSizes(DEFAULT); 127 setupAnimators(); 128 } 129 130 private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 131 double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 132 final Ring ring = mRing; 133 final DisplayMetrics metrics = mResources.getDisplayMetrics(); 134 final float screenDensity = metrics.density; 135 136 mWidth = progressCircleWidth * screenDensity; 137 mHeight = progressCircleHeight * screenDensity; 138 ring.setStrokeWidth((float) strokeWidth * screenDensity); 139 ring.setCenterRadius(centerRadius * screenDensity); 140 ring.setColorIndex(0); 141 ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 142 ring.setInsets((int) mWidth, (int) mHeight); 143 } 144 145 /** 146 * Set the overall size for the progress spinner. This updates the radius 147 * and stroke width of the ring. 148 * 149 * @param size One of {@link MaterialProgressDrawable.LARGE} or 150 * {@link MaterialProgressDrawable.DEFAULT} 151 */ 152 public void updateSizes(@ProgressDrawableSize int size) { 153 if (size == LARGE) { 154 setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 155 STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 156 } else { 157 setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 158 ARROW_WIDTH, ARROW_HEIGHT); 159 } 160 } 161 162 /** 163 * @param show Set to true to display the arrowhead on the progress spinner. 164 */ 165 public void showArrow(boolean show) { 166 mRing.setShowArrow(show); 167 } 168 169 /** 170 * @param scale Set the scale of the arrowhead for the spinner. 171 */ 172 public void setArrowScale(float scale) { 173 mRing.setArrowScale(scale); 174 } 175 176 /** 177 * Set the start and end trim for the progress spinner arc. 178 * 179 * @param startAngle start angle 180 * @param endAngle end angle 181 */ 182 public void setStartEndTrim(float startAngle, float endAngle) { 183 mRing.setStartTrim(startAngle); 184 mRing.setEndTrim(endAngle); 185 } 186 187 /** 188 * Set the amount of rotation to apply to the progress spinner. 189 * 190 * @param rotation Rotation is from [0..1] 191 */ 192 public void setProgressRotation(float rotation) { 193 mRing.setRotation(rotation); 194 } 195 196 /** 197 * Update the background color of the circle image view. 198 */ 199 public void setBackgroundColor(int color) { 200 mRing.setBackgroundColor(color); 201 } 202 203 /** 204 * Set the colors used in the progress animation from color resources. 205 * The first color will also be the color of the bar that grows in response 206 * to a user swipe gesture. 207 * 208 * @param colors 209 */ 210 public void setColorSchemeColors(int... colors) { 211 mRing.setColors(colors); 212 mRing.setColorIndex(0); 213 } 214 215 @Override 216 public int getIntrinsicHeight() { 217 return (int) mHeight; 218 } 219 220 @Override 221 public int getIntrinsicWidth() { 222 return (int) mWidth; 223 } 224 225 @Override 226 public void draw(Canvas c) { 227 final Rect bounds = getBounds(); 228 final int saveCount = c.save(); 229 c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 230 mRing.draw(c, bounds); 231 c.restoreToCount(saveCount); 232 } 233 234 @Override 235 public void setAlpha(int alpha) { 236 mRing.setAlpha(alpha); 237 } 238 239 public int getAlpha() { 240 return mRing.getAlpha(); 241 } 242 243 @Override 244 public void setColorFilter(ColorFilter colorFilter) { 245 mRing.setColorFilter(colorFilter); 246 } 247 248 @SuppressWarnings("unused") 249 void setRotation(float rotation) { 250 mRotation = rotation; 251 invalidateSelf(); 252 } 253 254 @SuppressWarnings("unused") 255 private float getRotation() { 256 return mRotation; 257 } 258 259 @Override 260 public int getOpacity() { 261 return PixelFormat.TRANSLUCENT; 262 } 263 264 @Override 265 public boolean isRunning() { 266 final ArrayList<Animation> animators = mAnimators; 267 final int N = animators.size(); 268 for (int i = 0; i < N; i++) { 269 final Animation animator = animators.get(i); 270 if (animator.hasStarted() && !animator.hasEnded()) { 271 return true; 272 } 273 } 274 return false; 275 } 276 277 @Override 278 public void start() { 279 mAnimation.reset(); 280 mRing.storeOriginals(); 281 // Already showing some part of the ring 282 if (mRing.getEndTrim() != mRing.getStartTrim()) { 283 mFinishing = true; 284 mAnimation.setDuration(ANIMATION_DURATION/2); 285 mParent.startAnimation(mAnimation); 286 } else { 287 mRing.setColorIndex(0); 288 mRing.resetOriginals(); 289 mAnimation.setDuration(ANIMATION_DURATION); 290 mParent.startAnimation(mAnimation); 291 } 292 } 293 294 @Override 295 public void stop() { 296 mParent.clearAnimation(); 297 setRotation(0); 298 mRing.setShowArrow(false); 299 mRing.setColorIndex(0); 300 mRing.resetOriginals(); 301 } 302 303 private float getMinProgressArc(Ring ring) { 304 return (float) Math.toRadians( 305 ring.getStrokeWidth() / (2 * Math.PI * ring.getCenterRadius())); 306 } 307 308 // Adapted from ArgbEvaluator.java 309 private int evaluateColorChange(float fraction, int startValue, int endValue) { 310 int startInt = (Integer) startValue; 311 int startA = (startInt >> 24) & 0xff; 312 int startR = (startInt >> 16) & 0xff; 313 int startG = (startInt >> 8) & 0xff; 314 int startB = startInt & 0xff; 315 316 int endInt = (Integer) endValue; 317 int endA = (endInt >> 24) & 0xff; 318 int endR = (endInt >> 16) & 0xff; 319 int endG = (endInt >> 8) & 0xff; 320 int endB = endInt & 0xff; 321 322 return (int)((startA + (int)(fraction * (endA - startA))) << 24) | 323 (int)((startR + (int)(fraction * (endR - startR))) << 16) | 324 (int)((startG + (int)(fraction * (endG - startG))) << 8) | 325 (int)((startB + (int)(fraction * (endB - startB)))); 326 } 327 328 /** 329 * Update the ring color if this is within the last 25% of the animation. 330 * The new ring color will be a translation from the starting ring color to 331 * the next color. 332 */ 333 private void updateRingColor(float interpolatedTime, Ring ring) { 334 if (interpolatedTime > COLOR_START_DELAY_OFFSET) { 335 // scale the interpolatedTime so that the full 336 // transformation from 0 - 1 takes place in the 337 // remaining time 338 ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET) 339 / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(), 340 ring.getNextColor())); 341 } 342 } 343 344 private void applyFinishTranslation(float interpolatedTime, Ring ring) { 345 // shrink back down and complete a full rotation before 346 // starting other circles 347 // Rotation goes between [0..1]. 348 updateRingColor(interpolatedTime, ring); 349 float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC) 350 + 1f); 351 final float minProgressArc = getMinProgressArc(ring); 352 final float startTrim = ring.getStartingStartTrim() 353 + (ring.getStartingEndTrim() - minProgressArc - ring.getStartingStartTrim()) 354 * interpolatedTime; 355 ring.setStartTrim(startTrim); 356 ring.setEndTrim(ring.getStartingEndTrim()); 357 final float rotation = ring.getStartingRotation() 358 + ((targetRotation - ring.getStartingRotation()) * interpolatedTime); 359 ring.setRotation(rotation); 360 } 361 362 private void setupAnimators() { 363 final Ring ring = mRing; 364 final Animation animation = new Animation() { 365 @Override 366 public void applyTransformation(float interpolatedTime, Transformation t) { 367 if (mFinishing) { 368 applyFinishTranslation(interpolatedTime, ring); 369 } else { 370 // The minProgressArc is calculated from 0 to create an 371 // angle that matches the stroke width. 372 final float minProgressArc = getMinProgressArc(ring); 373 final float startingEndTrim = ring.getStartingEndTrim(); 374 final float startingTrim = ring.getStartingStartTrim(); 375 final float startingRotation = ring.getStartingRotation(); 376 377 updateRingColor(interpolatedTime, ring); 378 379 // Moving the start trim only occurs in the first 50% of a 380 // single ring animation 381 if (interpolatedTime <= START_TRIM_DURATION_OFFSET) { 382 // scale the interpolatedTime so that the full 383 // transformation from 0 - 1 takes place in the 384 // remaining time 385 final float scaledTime = (interpolatedTime) 386 / (1.0f - START_TRIM_DURATION_OFFSET); 387 final float startTrim = startingTrim 388 + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR 389 .getInterpolation(scaledTime)); 390 ring.setStartTrim(startTrim); 391 } 392 393 // Moving the end trim starts after 50% of a single ring 394 // animation completes 395 if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) { 396 // scale the interpolatedTime so that the full 397 // transformation from 0 - 1 takes place in the 398 // remaining time 399 final float minArc = MAX_PROGRESS_ARC - minProgressArc; 400 float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET) 401 / (1.0f - START_TRIM_DURATION_OFFSET); 402 final float endTrim = startingEndTrim 403 + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime)); 404 ring.setEndTrim(endTrim); 405 } 406 407 final float rotation = startingRotation + (0.25f * interpolatedTime); 408 ring.setRotation(rotation); 409 410 float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime) 411 + (FULL_ROTATION * (mRotationCount / NUM_POINTS)); 412 setRotation(groupRotation); 413 } 414 } 415 }; 416 animation.setRepeatCount(Animation.INFINITE); 417 animation.setRepeatMode(Animation.RESTART); 418 animation.setInterpolator(LINEAR_INTERPOLATOR); 419 animation.setAnimationListener(new Animation.AnimationListener() { 420 421 @Override 422 public void onAnimationStart(Animation animation) { 423 mRotationCount = 0; 424 } 425 426 @Override 427 public void onAnimationEnd(Animation animation) { 428 // do nothing 429 } 430 431 @Override 432 public void onAnimationRepeat(Animation animation) { 433 ring.storeOriginals(); 434 ring.goToNextColor(); 435 ring.setStartTrim(ring.getEndTrim()); 436 if (mFinishing) { 437 // finished closing the last ring from the swipe gesture; go 438 // into progress mode 439 mFinishing = false; 440 animation.setDuration(ANIMATION_DURATION); 441 ring.setShowArrow(false); 442 } else { 443 mRotationCount = (mRotationCount + 1) % (NUM_POINTS); 444 } 445 } 446 }); 447 mAnimation = animation; 448 } 449 450 private final Callback mCallback = new Callback() { 451 @Override 452 public void invalidateDrawable(Drawable d) { 453 invalidateSelf(); 454 } 455 456 @Override 457 public void scheduleDrawable(Drawable d, Runnable what, long when) { 458 scheduleSelf(what, when); 459 } 460 461 @Override 462 public void unscheduleDrawable(Drawable d, Runnable what) { 463 unscheduleSelf(what); 464 } 465 }; 466 467 private static class Ring { 468 private final RectF mTempBounds = new RectF(); 469 private final Paint mPaint = new Paint(); 470 private final Paint mArrowPaint = new Paint(); 471 472 private final Callback mCallback; 473 474 private float mStartTrim = 0.0f; 475 private float mEndTrim = 0.0f; 476 private float mRotation = 0.0f; 477 private float mStrokeWidth = 5.0f; 478 private float mStrokeInset = 2.5f; 479 480 private int[] mColors; 481 // mColorIndex represents the offset into the available mColors that the 482 // progress circle should currently display. As the progress circle is 483 // animating, the mColorIndex moves by one to the next available color. 484 private int mColorIndex; 485 private float mStartingStartTrim; 486 private float mStartingEndTrim; 487 private float mStartingRotation; 488 private boolean mShowArrow; 489 private Path mArrow; 490 private float mArrowScale; 491 private double mRingCenterRadius; 492 private int mArrowWidth; 493 private int mArrowHeight; 494 private int mAlpha; 495 private final Paint mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 496 private int mBackgroundColor; 497 private int mCurrentColor; 498 499 public Ring(Callback callback) { 500 mCallback = callback; 501 502 mPaint.setStrokeCap(Paint.Cap.SQUARE); 503 mPaint.setAntiAlias(true); 504 mPaint.setStyle(Style.STROKE); 505 506 mArrowPaint.setStyle(Paint.Style.FILL); 507 mArrowPaint.setAntiAlias(true); 508 } 509 510 public void setBackgroundColor(int color) { 511 mBackgroundColor = color; 512 } 513 514 /** 515 * Set the dimensions of the arrowhead. 516 * 517 * @param width Width of the hypotenuse of the arrow head 518 * @param height Height of the arrow point 519 */ 520 public void setArrowDimensions(float width, float height) { 521 mArrowWidth = (int) width; 522 mArrowHeight = (int) height; 523 } 524 525 /** 526 * Draw the progress spinner 527 */ 528 public void draw(Canvas c, Rect bounds) { 529 final RectF arcBounds = mTempBounds; 530 arcBounds.set(bounds); 531 arcBounds.inset(mStrokeInset, mStrokeInset); 532 533 final float startAngle = (mStartTrim + mRotation) * 360; 534 final float endAngle = (mEndTrim + mRotation) * 360; 535 float sweepAngle = endAngle - startAngle; 536 537 mPaint.setColor(mCurrentColor); 538 c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint); 539 540 drawTriangle(c, startAngle, sweepAngle, bounds); 541 542 if (mAlpha < 255) { 543 mCirclePaint.setColor(mBackgroundColor); 544 mCirclePaint.setAlpha(255 - mAlpha); 545 c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2, 546 mCirclePaint); 547 } 548 } 549 550 private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) { 551 if (mShowArrow) { 552 if (mArrow == null) { 553 mArrow = new android.graphics.Path(); 554 mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD); 555 } else { 556 mArrow.reset(); 557 } 558 559 // Adjust the position of the triangle so that it is inset as 560 // much as the arc, but also centered on the arc. 561 float inset = (int) mStrokeInset / 2 * mArrowScale; 562 float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX()); 563 float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY()); 564 565 // Update the path each time. This works around an issue in SKIA 566 // where concatenating a rotation matrix to a scale matrix 567 // ignored a starting negative rotation. This appears to have 568 // been fixed as of API 21. 569 mArrow.moveTo(0, 0); 570 mArrow.lineTo(mArrowWidth * mArrowScale, 0); 571 mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight 572 * mArrowScale)); 573 mArrow.offset(x - inset, y); 574 mArrow.close(); 575 // draw a triangle 576 mArrowPaint.setColor(mCurrentColor); 577 c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(), 578 bounds.exactCenterY()); 579 c.drawPath(mArrow, mArrowPaint); 580 } 581 } 582 583 /** 584 * Set the colors the progress spinner alternates between. 585 * 586 * @param colors Array of integers describing the colors. Must be non-<code>null</code>. 587 */ 588 public void setColors(@NonNull int[] colors) { 589 mColors = colors; 590 // if colors are reset, make sure to reset the color index as well 591 setColorIndex(0); 592 } 593 594 /** 595 * Set the absolute color of the progress spinner. This is should only 596 * be used when animating between current and next color when the 597 * spinner is rotating. 598 * 599 * @param color int describing the color. 600 */ 601 public void setColor(int color) { 602 mCurrentColor = color; 603 } 604 605 /** 606 * @param index Index into the color array of the color to display in 607 * the progress spinner. 608 */ 609 public void setColorIndex(int index) { 610 mColorIndex = index; 611 mCurrentColor = mColors[mColorIndex]; 612 } 613 614 /** 615 * @return int describing the next color the progress spinner should use when drawing. 616 */ 617 public int getNextColor() { 618 return mColors[getNextColorIndex()]; 619 } 620 621 private int getNextColorIndex() { 622 return (mColorIndex + 1) % (mColors.length); 623 } 624 625 /** 626 * Proceed to the next available ring color. This will automatically 627 * wrap back to the beginning of colors. 628 */ 629 public void goToNextColor() { 630 setColorIndex(getNextColorIndex()); 631 } 632 633 public void setColorFilter(ColorFilter filter) { 634 mPaint.setColorFilter(filter); 635 invalidateSelf(); 636 } 637 638 /** 639 * @param alpha Set the alpha of the progress spinner and associated arrowhead. 640 */ 641 public void setAlpha(int alpha) { 642 mAlpha = alpha; 643 } 644 645 /** 646 * @return Current alpha of the progress spinner and arrowhead. 647 */ 648 public int getAlpha() { 649 return mAlpha; 650 } 651 652 /** 653 * @param strokeWidth Set the stroke width of the progress spinner in pixels. 654 */ 655 public void setStrokeWidth(float strokeWidth) { 656 mStrokeWidth = strokeWidth; 657 mPaint.setStrokeWidth(strokeWidth); 658 invalidateSelf(); 659 } 660 661 @SuppressWarnings("unused") 662 public float getStrokeWidth() { 663 return mStrokeWidth; 664 } 665 666 @SuppressWarnings("unused") 667 public void setStartTrim(float startTrim) { 668 mStartTrim = startTrim; 669 invalidateSelf(); 670 } 671 672 @SuppressWarnings("unused") 673 public float getStartTrim() { 674 return mStartTrim; 675 } 676 677 public float getStartingStartTrim() { 678 return mStartingStartTrim; 679 } 680 681 public float getStartingEndTrim() { 682 return mStartingEndTrim; 683 } 684 685 public int getStartingColor() { 686 return mColors[mColorIndex]; 687 } 688 689 @SuppressWarnings("unused") 690 public void setEndTrim(float endTrim) { 691 mEndTrim = endTrim; 692 invalidateSelf(); 693 } 694 695 @SuppressWarnings("unused") 696 public float getEndTrim() { 697 return mEndTrim; 698 } 699 700 @SuppressWarnings("unused") 701 public void setRotation(float rotation) { 702 mRotation = rotation; 703 invalidateSelf(); 704 } 705 706 @SuppressWarnings("unused") 707 public float getRotation() { 708 return mRotation; 709 } 710 711 public void setInsets(int width, int height) { 712 final float minEdge = (float) Math.min(width, height); 713 float insets; 714 if (mRingCenterRadius <= 0 || minEdge < 0) { 715 insets = (float) Math.ceil(mStrokeWidth / 2.0f); 716 } else { 717 insets = (float) (minEdge / 2.0f - mRingCenterRadius); 718 } 719 mStrokeInset = insets; 720 } 721 722 @SuppressWarnings("unused") 723 public float getInsets() { 724 return mStrokeInset; 725 } 726 727 /** 728 * @param centerRadius Inner radius in px of the circle the progress 729 * spinner arc traces. 730 */ 731 public void setCenterRadius(double centerRadius) { 732 mRingCenterRadius = centerRadius; 733 } 734 735 public double getCenterRadius() { 736 return mRingCenterRadius; 737 } 738 739 /** 740 * @param show Set to true to show the arrow head on the progress spinner. 741 */ 742 public void setShowArrow(boolean show) { 743 if (mShowArrow != show) { 744 mShowArrow = show; 745 invalidateSelf(); 746 } 747 } 748 749 /** 750 * @param scale Set the scale of the arrowhead for the spinner. 751 */ 752 public void setArrowScale(float scale) { 753 if (scale != mArrowScale) { 754 mArrowScale = scale; 755 invalidateSelf(); 756 } 757 } 758 759 /** 760 * @return The amount the progress spinner is currently rotated, between [0..1]. 761 */ 762 public float getStartingRotation() { 763 return mStartingRotation; 764 } 765 766 /** 767 * If the start / end trim are offset to begin with, store them so that 768 * animation starts from that offset. 769 */ 770 public void storeOriginals() { 771 mStartingStartTrim = mStartTrim; 772 mStartingEndTrim = mEndTrim; 773 mStartingRotation = mRotation; 774 } 775 776 /** 777 * Reset the progress spinner to default rotation, start and end angles. 778 */ 779 public void resetOriginals() { 780 mStartingStartTrim = 0; 781 mStartingEndTrim = 0; 782 mStartingRotation = 0; 783 setStartTrim(0); 784 setEndTrim(0); 785 setRotation(0); 786 } 787 788 private void invalidateSelf() { 789 mCallback.invalidateDrawable(null); 790 } 791 } 792} 793