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