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.content.Context; 20import android.content.res.Resources; 21import android.graphics.Canvas; 22import android.graphics.Color; 23import android.graphics.ColorFilter; 24import android.graphics.Paint; 25import android.graphics.Paint.Style; 26import android.graphics.Path; 27import android.graphics.PixelFormat; 28import android.graphics.Rect; 29import android.graphics.RectF; 30import android.graphics.drawable.Animatable; 31import android.graphics.drawable.Drawable; 32import android.support.annotation.IntDef; 33import android.support.annotation.NonNull; 34import android.support.v4.view.animation.FastOutSlowInInterpolator; 35import android.util.DisplayMetrics; 36import android.view.View; 37import android.view.animation.Animation; 38import android.view.animation.Interpolator; 39import android.view.animation.LinearInterpolator; 40import android.view.animation.Transformation; 41 42import java.lang.annotation.Retention; 43import java.lang.annotation.RetentionPolicy; 44import java.util.ArrayList; 45 46/** 47 * Fancy progress indicator for Material theme. 48 */ 49class MaterialProgressDrawable extends Drawable implements Animatable { 50 private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator(); 51 static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator(); 52 53 private static final float FULL_ROTATION = 1080.0f; 54 55 @Retention(RetentionPolicy.SOURCE) 56 @IntDef({LARGE, DEFAULT}) 57 public @interface ProgressDrawableSize {} 58 59 // Maps to ProgressBar.Large style 60 static final int LARGE = 0; 61 // Maps to ProgressBar default style 62 static final int DEFAULT = 1; 63 64 // Maps to ProgressBar default style 65 private static final int CIRCLE_DIAMETER = 40; 66 private static final float CENTER_RADIUS = 8.75f; //should add up to 10 when + stroke_width 67 private static final float STROKE_WIDTH = 2.5f; 68 69 // Maps to ProgressBar.Large style 70 private static final int CIRCLE_DIAMETER_LARGE = 56; 71 private static final float CENTER_RADIUS_LARGE = 12.5f; 72 private static final float STROKE_WIDTH_LARGE = 3f; 73 74 private static final int[] COLORS = new int[] { 75 Color.BLACK 76 }; 77 78 /** 79 * The value in the linear interpolator for animating the drawable at which 80 * the color transition should start 81 */ 82 private static final float COLOR_START_DELAY_OFFSET = 0.75f; 83 private static final float END_TRIM_START_DELAY_OFFSET = 0.5f; 84 private static final float START_TRIM_DURATION_OFFSET = 0.5f; 85 86 /** The duration of a single progress spin in milliseconds. */ 87 private static final int ANIMATION_DURATION = 1332; 88 89 /** The number of points in the progress "star". */ 90 private static final float NUM_POINTS = 5f; 91 /** The list of animators operating on this drawable. */ 92 private final ArrayList<Animation> mAnimators = new ArrayList<Animation>(); 93 94 /** The indicator ring, used to manage animation state. */ 95 private final Ring mRing; 96 97 /** Canvas rotation in degrees. */ 98 private float mRotation; 99 100 /** Layout info for the arrowhead in dp */ 101 private static final int ARROW_WIDTH = 10; 102 private static final int ARROW_HEIGHT = 5; 103 private static final float ARROW_OFFSET_ANGLE = 5; 104 105 /** Layout info for the arrowhead for the large spinner in dp */ 106 private static final int ARROW_WIDTH_LARGE = 12; 107 private static final int ARROW_HEIGHT_LARGE = 6; 108 private static final float MAX_PROGRESS_ARC = .8f; 109 110 private Resources mResources; 111 private View mParent; 112 private Animation mAnimation; 113 float mRotationCount; 114 private double mWidth; 115 private double mHeight; 116 boolean mFinishing; 117 118 MaterialProgressDrawable(Context context, View parent) { 119 mParent = parent; 120 mResources = context.getResources(); 121 122 mRing = new Ring(mCallback); 123 mRing.setColors(COLORS); 124 125 updateSizes(DEFAULT); 126 setupAnimators(); 127 } 128 129 private void setSizeParameters(double progressCircleWidth, double progressCircleHeight, 130 double centerRadius, double strokeWidth, float arrowWidth, float arrowHeight) { 131 final Ring ring = mRing; 132 final DisplayMetrics metrics = mResources.getDisplayMetrics(); 133 final float screenDensity = metrics.density; 134 135 mWidth = progressCircleWidth * screenDensity; 136 mHeight = progressCircleHeight * screenDensity; 137 ring.setStrokeWidth((float) strokeWidth * screenDensity); 138 ring.setCenterRadius(centerRadius * screenDensity); 139 ring.setColorIndex(0); 140 ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity); 141 ring.setInsets((int) mWidth, (int) mHeight); 142 } 143 144 /** 145 * Set the overall size for the progress spinner. This updates the radius 146 * and stroke width of the ring. 147 * 148 * @param size One of {@link MaterialProgressDrawable.LARGE} or 149 * {@link MaterialProgressDrawable.DEFAULT} 150 */ 151 public void updateSizes(@ProgressDrawableSize int size) { 152 if (size == LARGE) { 153 setSizeParameters(CIRCLE_DIAMETER_LARGE, CIRCLE_DIAMETER_LARGE, CENTER_RADIUS_LARGE, 154 STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE, ARROW_HEIGHT_LARGE); 155 } else { 156 setSizeParameters(CIRCLE_DIAMETER, CIRCLE_DIAMETER, CENTER_RADIUS, STROKE_WIDTH, 157 ARROW_WIDTH, ARROW_HEIGHT); 158 } 159 } 160 161 /** 162 * @param show Set to true to display the arrowhead on the progress spinner. 163 */ 164 public void showArrow(boolean show) { 165 mRing.setShowArrow(show); 166 } 167 168 /** 169 * @param scale Set the scale of the arrowhead for the spinner. 170 */ 171 public void setArrowScale(float scale) { 172 mRing.setArrowScale(scale); 173 } 174 175 /** 176 * Set the start and end trim for the progress spinner arc. 177 * 178 * @param startAngle start angle 179 * @param endAngle end angle 180 */ 181 public void setStartEndTrim(float startAngle, float endAngle) { 182 mRing.setStartTrim(startAngle); 183 mRing.setEndTrim(endAngle); 184 } 185 186 /** 187 * Set the amount of rotation to apply to the progress spinner. 188 * 189 * @param rotation Rotation is from [0..1] 190 */ 191 public void setProgressRotation(float rotation) { 192 mRing.setRotation(rotation); 193 } 194 195 /** 196 * Update the background color of the circle image view. 197 */ 198 public void setBackgroundColor(int color) { 199 mRing.setBackgroundColor(color); 200 } 201 202 /** 203 * Set the colors used in the progress animation from color resources. 204 * The first color will also be the color of the bar that grows in response 205 * to a user swipe gesture. 206 * 207 * @param colors 208 */ 209 public void setColorSchemeColors(int... colors) { 210 mRing.setColors(colors); 211 mRing.setColorIndex(0); 212 } 213 214 @Override 215 public int getIntrinsicHeight() { 216 return (int) mHeight; 217 } 218 219 @Override 220 public int getIntrinsicWidth() { 221 return (int) mWidth; 222 } 223 224 @Override 225 public void draw(Canvas c) { 226 final Rect bounds = getBounds(); 227 final int saveCount = c.save(); 228 c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY()); 229 mRing.draw(c, bounds); 230 c.restoreToCount(saveCount); 231 } 232 233 @Override 234 public void setAlpha(int alpha) { 235 mRing.setAlpha(alpha); 236 } 237 238 @Override 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 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 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 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 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