java.lang.Object
↳Drawable
↳androidx.swiperefreshlayout.widget.CircularProgressDrawable
Gradle dependencies
compile group: 'androidx.swiperefreshlayout', name: 'swiperefreshlayout', version: '1.2.0-alpha01'
- groupId: androidx.swiperefreshlayout
- artifactId: swiperefreshlayout
- version: 1.2.0-alpha01
Artifact androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01 it located at Google repository (https://maven.google.com/)
Androidx artifact mapping:
androidx.swiperefreshlayout:swiperefreshlayout com.android.support:swiperefreshlayout
Androidx class mapping:
androidx.swiperefreshlayout.widget.CircularProgressDrawable android.support.v4.widget.CircularProgressDrawable
Overview
Drawable that renders the animated indeterminate progress indicator in the Material design style
without depending on API level 11.
While this may be used to draw an indeterminate spinner using CircularProgressDrawable.start() and CircularProgressDrawable.stop() methods, this may also be used to draw a progress arc using CircularProgressDrawable.setStartEndTrim(float, float) method. CircularProgressDrawable also supports adding an arrow
at the end of the arc by CircularProgressDrawable.setArrowEnabled(boolean) and CircularProgressDrawable.setArrowDimensions(float, float) methods.
To use one of the pre-defined sizes instead of using your own, CircularProgressDrawable.setStyle(int) should
be called with one of the CircularProgressDrawable.DEFAULT or CircularProgressDrawable.LARGE styles as its parameter. Doing it
so will update the arrow dimensions, ring size and stroke width to fit the one specified.
If no center radius is set via CircularProgressDrawable.setCenterRadius(float) or CircularProgressDrawable.setStyle(int)
methods, CircularProgressDrawable will fill the bounds set via CircularProgressDrawable.
Summary
Fields |
---|
public static final int | DEFAULT Maps to ProgressBar default style. |
public static final int | LARGE Maps to ProgressBar.Large style. |
Methods |
---|
public void | draw(Canvas canvas)
|
public int | getAlpha()
|
public boolean | getArrowEnabled()
Returns true if the arrow at the end of the spinner is shown. |
public float | getArrowHeight()
Returns the arrow height in pixels. |
public float | getArrowScale()
Returns the scale of the arrow at the end of the spinner. |
public float | getArrowWidth()
Returns the arrow width in pixels. |
public int | getBackgroundColor()
Returns the background color of the circle drawn inside the drawable. |
public float | getCenterRadius()
Returns the center radius for the progress spinner in pixels. |
public int[] | getColorSchemeColors()
Returns the colors used in the progress animation |
public float | getEndTrim()
Returns the end trim for the progress spinner arc |
public int | getOpacity()
|
public float | getProgressRotation()
Returns the amount of rotation applied to the progress spinner. |
public float | getStartTrim()
Returns the start trim for the progress spinner arc |
public Paint.Cap | getStrokeCap()
Returns the stroke cap of the progress spinner. |
public float | getStrokeWidth()
Returns the stroke width for the progress spinner in pixels. |
public boolean | isRunning()
|
public void | setAlpha(int alpha)
|
public void | setArrowDimensions(float width, float height)
Sets the dimensions of the arrow at the end of the spinner in pixels. |
public void | setArrowEnabled(boolean show)
Sets if the arrow at the end of the spinner should be shown. |
public void | setArrowScale(float scale)
Sets the scale of the arrow at the end of the spinner. |
public void | setBackgroundColor(int color)
Sets the background color of the circle inside the drawable. |
public void | setCenterRadius(float centerRadius)
Sets the center radius for the progress spinner in pixels. |
public void | setColorFilter(ColorFilter colorFilter)
|
public void | setColorSchemeColors(int[] colors[])
Sets the colors used in the progress animation from a color list. |
public void | setProgressRotation(float rotation)
Sets the amount of rotation to apply to the progress spinner. |
public void | setStartEndTrim(float start, float end)
Sets the start and end trim for the progress spinner arc. |
public void | setStrokeCap(Paint.Cap strokeCap)
Sets the stroke cap of the progress spinner. |
public void | setStrokeWidth(float strokeWidth)
Sets the stroke width for the progress spinner in pixels. |
public void | setStyle(int size)
Sets the overall size for the progress spinner. |
public void | start()
Starts the animation for the spinner. |
public void | stop()
Stops the animation for the spinner. |
from java.lang.Object | clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Fields
public static final int
LARGEMaps to ProgressBar.Large style.
public static final int
DEFAULTMaps to ProgressBar default style.
Constructors
public
CircularProgressDrawable(Context context)
Parameters:
context: application context
Methods
public void
setStyle(int size)
Sets the overall size for the progress spinner. This updates the radius
and stroke width of the ring, and arrow dimensions.
Parameters:
size: one of CircularProgressDrawable.LARGE or CircularProgressDrawable.DEFAULT
public float
getStrokeWidth()
Returns the stroke width for the progress spinner in pixels.
Returns:
stroke width in pixels
public void
setStrokeWidth(float strokeWidth)
Sets the stroke width for the progress spinner in pixels.
Parameters:
strokeWidth: stroke width in pixels
public float
getCenterRadius()
Returns the center radius for the progress spinner in pixels.
Returns:
center radius in pixels
public void
setCenterRadius(float centerRadius)
Sets the center radius for the progress spinner in pixels. If set to 0, this drawable will
fill the bounds when drawn.
Parameters:
centerRadius: center radius in pixels
public void
setStrokeCap(Paint.Cap strokeCap)
Sets the stroke cap of the progress spinner. Default stroke cap is .
Parameters:
strokeCap: stroke cap
public Paint.Cap
getStrokeCap()
Returns the stroke cap of the progress spinner.
Returns:
stroke cap
public float
getArrowWidth()
Returns the arrow width in pixels.
Returns:
arrow width in pixels
public float
getArrowHeight()
Returns the arrow height in pixels.
Returns:
arrow height in pixels
public void
setArrowDimensions(float width, float height)
Sets the dimensions of the arrow at the end of the spinner in pixels.
Parameters:
width: width of the baseline of the arrow in pixels
height: distance from tip of the arrow to its baseline in pixels
public boolean
getArrowEnabled()
Returns true if the arrow at the end of the spinner is shown.
Returns:
true if the arrow is shown, false otherwise.
public void
setArrowEnabled(boolean show)
Sets if the arrow at the end of the spinner should be shown.
Parameters:
show: true if the arrow should be drawn, false otherwise
public float
getArrowScale()
Returns the scale of the arrow at the end of the spinner.
Returns:
scale of the arrow
public void
setArrowScale(float scale)
Sets the scale of the arrow at the end of the spinner.
Parameters:
scale: scaling that will be applied to the arrow's both width and height when drawing.
public float
getStartTrim()
Returns the start trim for the progress spinner arc
Returns:
start trim from [0..1]
public float
getEndTrim()
Returns the end trim for the progress spinner arc
Returns:
end trim from [0..1]
public void
setStartEndTrim(float start, float end)
Sets the start and end trim for the progress spinner arc. 0 corresponds to the geometric
angle of 0 degrees (3 o'clock on a watch) and it increases clockwise, coming to a full circle
at 1.
Parameters:
start: starting position of the arc from [0..1]
end: ending position of the arc from [0..1]
public float
getProgressRotation()
Returns the amount of rotation applied to the progress spinner.
Returns:
amount of rotation from [0..1]
public void
setProgressRotation(float rotation)
Sets the amount of rotation to apply to the progress spinner.
Parameters:
rotation: rotation from [0..1]
public int
getBackgroundColor()
Returns the background color of the circle drawn inside the drawable.
Returns:
an ARGB color
public void
setBackgroundColor(int color)
Sets the background color of the circle inside the drawable. Calling CircularProgressDrawable.setAlpha(int) does not affect the visibility background color, so it should be set
separately if it needs to be hidden or visible.
Parameters:
color: an ARGB color
public int[]
getColorSchemeColors()
Returns the colors used in the progress animation
Returns:
list of ARGB colors
public void
setColorSchemeColors(int[] colors[])
Sets the colors used in the progress animation from a color list. The first color will also
be the color to be used if animation is not started yet.
Parameters:
colors: list of ARGB colors to be used in the spinner
public void
draw(Canvas canvas)
public void
setAlpha(int alpha)
public void
setColorFilter(ColorFilter colorFilter)
public boolean
isRunning()
Starts the animation for the spinner.
Stops the animation for the spinner.
Source
/*
* Copyright 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.swiperefreshlayout.widget;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.RestrictTo;
import androidx.core.util.Preconditions;
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Drawable that renders the animated indeterminate progress indicator in the Material design style
* without depending on API level 11.
*
* <p>While this may be used to draw an indeterminate spinner using {@link #start()} and {@link
* #stop()} methods, this may also be used to draw a progress arc using {@link
* #setStartEndTrim(float, float)} method. CircularProgressDrawable also supports adding an arrow
* at the end of the arc by {@link #setArrowEnabled(boolean)} and {@link #setArrowDimensions(float,
* float)} methods.
*
* <p>To use one of the pre-defined sizes instead of using your own, {@link #setStyle(int)} should
* be called with one of the {@link #DEFAULT} or {@link #LARGE} styles as its parameter. Doing it
* so will update the arrow dimensions, ring size and stroke width to fit the one specified.
*
* <p>If no center radius is set via {@link #setCenterRadius(float)} or {@link #setStyle(int)}
* methods, CircularProgressDrawable will fill the bounds set via {@link #setBounds(Rect)}.
*/
public class CircularProgressDrawable extends Drawable implements Animatable {
private static final Interpolator LINEAR_INTERPOLATOR = new LinearInterpolator();
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
/** @hide */
@RestrictTo(LIBRARY_GROUP_PREFIX)
@Retention(RetentionPolicy.SOURCE)
@IntDef({LARGE, DEFAULT})
public @interface ProgressDrawableSize {
}
/** Maps to ProgressBar.Large style. */
public static final int LARGE = 0;
private static final float CENTER_RADIUS_LARGE = 11f;
private static final float STROKE_WIDTH_LARGE = 3f;
private static final int ARROW_WIDTH_LARGE = 12;
private static final int ARROW_HEIGHT_LARGE = 6;
/** Maps to ProgressBar default style. */
public static final int DEFAULT = 1;
private static final float CENTER_RADIUS = 7.5f;
private static final float STROKE_WIDTH = 2.5f;
private static final int ARROW_WIDTH = 10;
private static final int ARROW_HEIGHT = 5;
/**
* This is the default set of colors that's used in spinner. {@link
* #setColorSchemeColors(int...)} allows modifying colors.
*/
private static final int[] COLORS = new int[]{
Color.BLACK
};
/**
* The value in the linear interpolator for animating the drawable at which
* the color transition should start
*/
private static final float COLOR_CHANGE_OFFSET = 0.75f;
private static final float SHRINK_OFFSET = 0.5f;
/** The duration of a single progress spin in milliseconds. */
private static final int ANIMATION_DURATION = 1332;
/** Full rotation that's done for the animation duration in degrees. */
private static final float GROUP_FULL_ROTATION = 1080f / 5f;
/** The indicator ring, used to manage animation state. */
private final Ring mRing;
/** Canvas rotation in degrees. */
private float mRotation;
/** Maximum length of the progress arc during the animation. */
private static final float MAX_PROGRESS_ARC = .8f;
/** Minimum length of the progress arc during the animation. */
private static final float MIN_PROGRESS_ARC = .01f;
/** Rotation applied to ring during the animation, to complete it to a full circle. */
private static final float RING_ROTATION = 1f - (MAX_PROGRESS_ARC - MIN_PROGRESS_ARC);
private Resources mResources;
private Animator mAnimator;
@SuppressWarnings("WeakerAccess") /* synthetic access */
float mRotationCount;
@SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mFinishing;
/**
* @param context application context
*/
public CircularProgressDrawable(@NonNull Context context) {
mResources = Preconditions.checkNotNull(context).getResources();
mRing = new Ring();
mRing.setColors(COLORS);
setStrokeWidth(STROKE_WIDTH);
setupAnimators();
}
/** Sets all parameters at once in dp. */
private void setSizeParameters(float centerRadius, float strokeWidth, float arrowWidth,
float arrowHeight) {
final Ring ring = mRing;
final DisplayMetrics metrics = mResources.getDisplayMetrics();
final float screenDensity = metrics.density;
ring.setStrokeWidth(strokeWidth * screenDensity);
ring.setCenterRadius(centerRadius * screenDensity);
ring.setColorIndex(0);
ring.setArrowDimensions(arrowWidth * screenDensity, arrowHeight * screenDensity);
}
/**
* Sets the overall size for the progress spinner. This updates the radius
* and stroke width of the ring, and arrow dimensions.
*
* @param size one of {@link #LARGE} or {@link #DEFAULT}
*/
public void setStyle(@ProgressDrawableSize int size) {
if (size == LARGE) {
setSizeParameters(CENTER_RADIUS_LARGE, STROKE_WIDTH_LARGE, ARROW_WIDTH_LARGE,
ARROW_HEIGHT_LARGE);
} else {
setSizeParameters(CENTER_RADIUS, STROKE_WIDTH, ARROW_WIDTH, ARROW_HEIGHT);
}
invalidateSelf();
}
/**
* Returns the stroke width for the progress spinner in pixels.
*
* @return stroke width in pixels
*/
public float getStrokeWidth() {
return mRing.getStrokeWidth();
}
/**
* Sets the stroke width for the progress spinner in pixels.
*
* @param strokeWidth stroke width in pixels
*/
public void setStrokeWidth(float strokeWidth) {
mRing.setStrokeWidth(strokeWidth);
invalidateSelf();
}
/**
* Returns the center radius for the progress spinner in pixels.
*
* @return center radius in pixels
*/
public float getCenterRadius() {
return mRing.getCenterRadius();
}
/**
* Sets the center radius for the progress spinner in pixels. If set to 0, this drawable will
* fill the bounds when drawn.
*
* @param centerRadius center radius in pixels
*/
public void setCenterRadius(float centerRadius) {
mRing.setCenterRadius(centerRadius);
invalidateSelf();
}
/**
* Sets the stroke cap of the progress spinner. Default stroke cap is {@link Paint.Cap#SQUARE}.
*
* @param strokeCap stroke cap
*/
public void setStrokeCap(@NonNull Paint.Cap strokeCap) {
mRing.setStrokeCap(strokeCap);
invalidateSelf();
}
/**
* Returns the stroke cap of the progress spinner.
*
* @return stroke cap
*/
@NonNull
public Paint.Cap getStrokeCap() {
return mRing.getStrokeCap();
}
/**
* Returns the arrow width in pixels.
*
* @return arrow width in pixels
*/
public float getArrowWidth() {
return mRing.getArrowWidth();
}
/**
* Returns the arrow height in pixels.
*
* @return arrow height in pixels
*/
public float getArrowHeight() {
return mRing.getArrowHeight();
}
/**
* Sets the dimensions of the arrow at the end of the spinner in pixels.
*
* @param width width of the baseline of the arrow in pixels
* @param height distance from tip of the arrow to its baseline in pixels
*/
public void setArrowDimensions(float width, float height) {
mRing.setArrowDimensions(width, height);
invalidateSelf();
}
/**
* Returns {@code true} if the arrow at the end of the spinner is shown.
*
* @return {@code true} if the arrow is shown, {@code false} otherwise.
*/
public boolean getArrowEnabled() {
return mRing.getShowArrow();
}
/**
* Sets if the arrow at the end of the spinner should be shown.
*
* @param show {@code true} if the arrow should be drawn, {@code false} otherwise
*/
public void setArrowEnabled(boolean show) {
mRing.setShowArrow(show);
invalidateSelf();
}
/**
* Returns the scale of the arrow at the end of the spinner.
*
* @return scale of the arrow
*/
public float getArrowScale() {
return mRing.getArrowScale();
}
/**
* Sets the scale of the arrow at the end of the spinner.
*
* @param scale scaling that will be applied to the arrow's both width and height when drawing.
*/
public void setArrowScale(float scale) {
mRing.setArrowScale(scale);
invalidateSelf();
}
/**
* Returns the start trim for the progress spinner arc
*
* @return start trim from [0..1]
*/
public float getStartTrim() {
return mRing.getStartTrim();
}
/**
* Returns the end trim for the progress spinner arc
*
* @return end trim from [0..1]
*/
public float getEndTrim() {
return mRing.getEndTrim();
}
/**
* Sets the start and end trim for the progress spinner arc. 0 corresponds to the geometric
* angle of 0 degrees (3 o'clock on a watch) and it increases clockwise, coming to a full circle
* at 1.
*
* @param start starting position of the arc from [0..1]
* @param end ending position of the arc from [0..1]
*/
public void setStartEndTrim(float start, float end) {
mRing.setStartTrim(start);
mRing.setEndTrim(end);
invalidateSelf();
}
/**
* Returns the amount of rotation applied to the progress spinner.
*
* @return amount of rotation from [0..1]
*/
public float getProgressRotation() {
return mRing.getRotation();
}
/**
* Sets the amount of rotation to apply to the progress spinner.
*
* @param rotation rotation from [0..1]
*/
public void setProgressRotation(float rotation) {
mRing.setRotation(rotation);
invalidateSelf();
}
/**
* Returns the background color of the circle drawn inside the drawable.
*
* @return an ARGB color
*/
public int getBackgroundColor() {
return mRing.getBackgroundColor();
}
/**
* Sets the background color of the circle inside the drawable. Calling {@link
* #setAlpha(int)} does not affect the visibility background color, so it should be set
* separately if it needs to be hidden or visible.
*
* @param color an ARGB color
*/
public void setBackgroundColor(int color) {
mRing.setBackgroundColor(color);
invalidateSelf();
}
/**
* Returns the colors used in the progress animation
*
* @return list of ARGB colors
*/
@NonNull
public int[] getColorSchemeColors() {
return mRing.getColors();
}
/**
* Sets the colors used in the progress animation from a color list. The first color will also
* be the color to be used if animation is not started yet.
*
* @param colors list of ARGB colors to be used in the spinner
*/
public void setColorSchemeColors(@NonNull int... colors) {
mRing.setColors(colors);
mRing.setColorIndex(0);
invalidateSelf();
}
@Override
public void draw(Canvas canvas) {
final Rect bounds = getBounds();
canvas.save();
canvas.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());
mRing.draw(canvas, bounds);
canvas.restore();
}
@Override
public void setAlpha(int alpha) {
mRing.setAlpha(alpha);
invalidateSelf();
}
@Override
public int getAlpha() {
return mRing.getAlpha();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mRing.setColorFilter(colorFilter);
invalidateSelf();
}
private void setRotation(float rotation) {
mRotation = rotation;
}
@SuppressWarnings("UnusedMethod") // TODO(b/141954576): Suppressed during upgrade to AGP 3.6.
private float getRotation() {
return mRotation;
}
@Override
@SuppressWarnings("deprecation")
// Remove suppression was b/120985527 is addressed.
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public boolean isRunning() {
return mAnimator.isRunning();
}
/**
* Starts the animation for the spinner.
*/
@Override
public void start() {
mAnimator.cancel();
mRing.storeOriginals();
// Already showing some part of the ring
if (mRing.getEndTrim() != mRing.getStartTrim()) {
mFinishing = true;
mAnimator.setDuration(ANIMATION_DURATION / 2);
mAnimator.start();
} else {
mRing.setColorIndex(0);
mRing.resetOriginals();
mAnimator.setDuration(ANIMATION_DURATION);
mAnimator.start();
}
}
/**
* Stops the animation for the spinner.
*/
@Override
public void stop() {
mAnimator.cancel();
setRotation(0);
mRing.setShowArrow(false);
mRing.setColorIndex(0);
mRing.resetOriginals();
invalidateSelf();
}
// Adapted from ArgbEvaluator.java
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff;
int startR = (startValue >> 16) & 0xff;
int startG = (startValue >> 8) & 0xff;
int startB = startValue & 0xff;
int endA = (endValue >> 24) & 0xff;
int endR = (endValue >> 16) & 0xff;
int endG = (endValue >> 8) & 0xff;
int endB = endValue & 0xff;
return (startA + (int) (fraction * (endA - startA))) << 24
| (startR + (int) (fraction * (endR - startR))) << 16
| (startG + (int) (fraction * (endG - startG))) << 8
| (startB + (int) (fraction * (endB - startB)));
}
/**
* Update the ring color if this is within the last 25% of the animation.
* The new ring color will be a translation from the starting ring color to
* the next color.
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void updateRingColor(float interpolatedTime, Ring ring) {
if (interpolatedTime > COLOR_CHANGE_OFFSET) {
ring.setColor(evaluateColorChange((interpolatedTime - COLOR_CHANGE_OFFSET)
/ (1f - COLOR_CHANGE_OFFSET), ring.getStartingColor(),
ring.getNextColor()));
} else {
ring.setColor(ring.getStartingColor());
}
}
/**
* Update the ring start and end trim if the animation is finishing (i.e. it started with
* already visible progress, so needs to shrink back down before starting the spinner).
*/
private void applyFinishTranslation(float interpolatedTime, Ring ring) {
// shrink back down and complete a full rotation before
// starting other circles
// Rotation goes between [0..1].
updateRingColor(interpolatedTime, ring);
float targetRotation = (float) (Math.floor(ring.getStartingRotation() / MAX_PROGRESS_ARC)
+ 1f);
final float startTrim = ring.getStartingStartTrim()
+ (ring.getStartingEndTrim() - MIN_PROGRESS_ARC - ring.getStartingStartTrim())
* interpolatedTime;
ring.setStartTrim(startTrim);
ring.setEndTrim(ring.getStartingEndTrim());
final float rotation = ring.getStartingRotation()
+ ((targetRotation - ring.getStartingRotation()) * interpolatedTime);
ring.setRotation(rotation);
}
/**
* Update the ring start and end trim according to current time of the animation.
*/
@SuppressWarnings("WeakerAccess") /* synthetic access */
void applyTransformation(float interpolatedTime, Ring ring, boolean lastFrame) {
if (mFinishing) {
applyFinishTranslation(interpolatedTime, ring);
// Below condition is to work around a ValueAnimator issue where onAnimationRepeat is
// called before last frame (1f).
} else if (interpolatedTime != 1f || lastFrame) {
final float startingRotation = ring.getStartingRotation();
float startTrim, endTrim;
if (interpolatedTime < SHRINK_OFFSET) { // Expansion occurs on first half of animation
final float scaledTime = interpolatedTime / SHRINK_OFFSET;
startTrim = ring.getStartingStartTrim();
endTrim = startTrim + ((MAX_PROGRESS_ARC - MIN_PROGRESS_ARC)
* MATERIAL_INTERPOLATOR.getInterpolation(scaledTime) + MIN_PROGRESS_ARC);
} else { // Shrinking occurs on second half of animation
float scaledTime = (interpolatedTime - SHRINK_OFFSET) / (1f - SHRINK_OFFSET);
endTrim = ring.getStartingStartTrim() + (MAX_PROGRESS_ARC - MIN_PROGRESS_ARC);
startTrim = endTrim - ((MAX_PROGRESS_ARC - MIN_PROGRESS_ARC)
* (1f - MATERIAL_INTERPOLATOR.getInterpolation(scaledTime))
+ MIN_PROGRESS_ARC);
}
final float rotation = startingRotation + (RING_ROTATION * interpolatedTime);
float groupRotation = GROUP_FULL_ROTATION * (interpolatedTime + mRotationCount);
ring.setStartTrim(startTrim);
ring.setEndTrim(endTrim);
ring.setRotation(rotation);
setRotation(groupRotation);
}
}
private void setupAnimators() {
final Ring ring = mRing;
final ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float interpolatedTime = (float) animation.getAnimatedValue();
updateRingColor(interpolatedTime, ring);
applyTransformation(interpolatedTime, ring, false);
invalidateSelf();
}
});
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(LINEAR_INTERPOLATOR);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
mRotationCount = 0;
}
@Override
public void onAnimationEnd(Animator animator) {
// do nothing
}
@Override
public void onAnimationCancel(Animator animation) {
// do nothing
}
@Override
public void onAnimationRepeat(Animator animator) {
applyTransformation(1f, ring, true);
ring.storeOriginals();
ring.goToNextColor();
if (mFinishing) {
// finished closing the last ring from the swipe gesture; go
// into progress mode
mFinishing = false;
animator.cancel();
animator.setDuration(ANIMATION_DURATION);
animator.start();
ring.setShowArrow(false);
} else {
mRotationCount = mRotationCount + 1;
}
}
});
mAnimator = animator;
}
/**
* A private class to do all the drawing of CircularProgressDrawable, which includes background,
* progress spinner and the arrow. This class is to separate drawing from animation.
*/
private static class Ring {
final RectF mTempBounds = new RectF();
final Paint mPaint = new Paint();
final Paint mArrowPaint = new Paint();
final Paint mCirclePaint = new Paint();
float mStartTrim = 0f;
float mEndTrim = 0f;
float mRotation = 0f;
float mStrokeWidth = 5f;
int[] mColors;
// mColorIndex represents the offset into the available mColors that the
// progress circle should currently display. As the progress circle is
// animating, the mColorIndex moves by one to the next available color.
int mColorIndex;
float mStartingStartTrim;
float mStartingEndTrim;
float mStartingRotation;
boolean mShowArrow;
Path mArrow;
float mArrowScale = 1;
float mRingCenterRadius;
int mArrowWidth;
int mArrowHeight;
int mAlpha = 255;
int mCurrentColor;
Ring() {
mPaint.setStrokeCap(Paint.Cap.SQUARE);
mPaint.setAntiAlias(true);
mPaint.setStyle(Style.STROKE);
mArrowPaint.setStyle(Paint.Style.FILL);
mArrowPaint.setAntiAlias(true);
mCirclePaint.setColor(Color.TRANSPARENT);
}
/**
* Sets the dimensions of the arrowhead.
*
* @param width width of the hypotenuse of the arrow head
* @param height height of the arrow point
*/
void setArrowDimensions(float width, float height) {
mArrowWidth = (int) width;
mArrowHeight = (int) height;
}
void setStrokeCap(Paint.Cap strokeCap) {
mPaint.setStrokeCap(strokeCap);
}
Paint.Cap getStrokeCap() {
return mPaint.getStrokeCap();
}
float getArrowWidth() {
return mArrowWidth;
}
float getArrowHeight() {
return mArrowHeight;
}
/**
* Draw the progress spinner
*/
void draw(Canvas c, Rect bounds) {
final RectF arcBounds = mTempBounds;
float arcRadius = mRingCenterRadius + mStrokeWidth / 2f;
if (mRingCenterRadius <= 0) {
// If center radius is not set, fill the bounds
arcRadius = Math.min(bounds.width(), bounds.height()) / 2f - Math.max(
(mArrowWidth * mArrowScale) / 2f, mStrokeWidth / 2f);
}
arcBounds.set(bounds.centerX() - arcRadius,
bounds.centerY() - arcRadius,
bounds.centerX() + arcRadius,
bounds.centerY() + arcRadius);
final float startAngle = (mStartTrim + mRotation) * 360;
final float endAngle = (mEndTrim + mRotation) * 360;
float sweepAngle = endAngle - startAngle;
mPaint.setColor(mCurrentColor);
mPaint.setAlpha(mAlpha);
// Draw the background first
float inset = mStrokeWidth / 2f; // Calculate inset to draw inside the arc
arcBounds.inset(inset, inset); // Apply inset
c.drawCircle(arcBounds.centerX(), arcBounds.centerY(), arcBounds.width() / 2f,
mCirclePaint);
arcBounds.inset(-inset, -inset); // Revert the inset
c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
drawTriangle(c, startAngle, sweepAngle, arcBounds);
}
void drawTriangle(Canvas c, float startAngle, float sweepAngle, RectF bounds) {
if (mShowArrow) {
if (mArrow == null) {
mArrow = new android.graphics.Path();
mArrow.setFillType(android.graphics.Path.FillType.EVEN_ODD);
} else {
mArrow.reset();
}
float centerRadius = Math.min(bounds.width(), bounds.height()) / 2f;
float inset = mArrowWidth * mArrowScale / 2f;
// Update the path each time. This works around an issue in SKIA
// where concatenating a rotation matrix to a scale matrix
// ignored a starting negative rotation. This appears to have
// been fixed as of API 21.
mArrow.moveTo(0, 0);
mArrow.lineTo(mArrowWidth * mArrowScale, 0);
mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
* mArrowScale));
mArrow.offset(centerRadius + bounds.centerX() - inset,
bounds.centerY() + mStrokeWidth / 2f);
mArrow.close();
// draw a triangle
mArrowPaint.setColor(mCurrentColor);
mArrowPaint.setAlpha(mAlpha);
c.save();
c.rotate(startAngle + sweepAngle, bounds.centerX(),
bounds.centerY());
c.drawPath(mArrow, mArrowPaint);
c.restore();
}
}
/**
* Sets the colors the progress spinner alternates between.
*
* @param colors array of ARGB colors. Must be non-{@code null}.
*/
void setColors(@NonNull int[] colors) {
mColors = colors;
// if colors are reset, make sure to reset the color index as well
setColorIndex(0);
}
int[] getColors() {
return mColors;
}
/**
* Sets the absolute color of the progress spinner. This is should only
* be used when animating between current and next color when the
* spinner is rotating.
*
* @param color an ARGB color
*/
void setColor(int color) {
mCurrentColor = color;
}
/**
* Sets the background color of the circle inside the spinner.
*/
void setBackgroundColor(int color) {
mCirclePaint.setColor(color);
}
int getBackgroundColor() {
return mCirclePaint.getColor();
}
/**
* @param index index into the color array of the color to display in
* the progress spinner.
*/
void setColorIndex(int index) {
mColorIndex = index;
mCurrentColor = mColors[mColorIndex];
}
/**
* @return int describing the next color the progress spinner should use when drawing.
*/
int getNextColor() {
return mColors[getNextColorIndex()];
}
int getNextColorIndex() {
return (mColorIndex + 1) % mColors.length;
}
/**
* Proceed to the next available ring color. This will automatically
* wrap back to the beginning of colors.
*/
void goToNextColor() {
setColorIndex(getNextColorIndex());
}
void setColorFilter(ColorFilter filter) {
mPaint.setColorFilter(filter);
}
/**
* @param alpha alpha of the progress spinner and associated arrowhead.
*/
void setAlpha(int alpha) {
mAlpha = alpha;
}
/**
* @return current alpha of the progress spinner and arrowhead
*/
int getAlpha() {
return mAlpha;
}
/**
* @param strokeWidth set the stroke width of the progress spinner in pixels.
*/
void setStrokeWidth(float strokeWidth) {
mStrokeWidth = strokeWidth;
mPaint.setStrokeWidth(strokeWidth);
}
float getStrokeWidth() {
return mStrokeWidth;
}
void setStartTrim(float startTrim) {
mStartTrim = startTrim;
}
float getStartTrim() {
return mStartTrim;
}
float getStartingStartTrim() {
return mStartingStartTrim;
}
float getStartingEndTrim() {
return mStartingEndTrim;
}
int getStartingColor() {
return mColors[mColorIndex];
}
void setEndTrim(float endTrim) {
mEndTrim = endTrim;
}
float getEndTrim() {
return mEndTrim;
}
void setRotation(float rotation) {
mRotation = rotation;
}
float getRotation() {
return mRotation;
}
/**
* @param centerRadius inner radius in px of the circle the progress spinner arc traces
*/
void setCenterRadius(float centerRadius) {
mRingCenterRadius = centerRadius;
}
float getCenterRadius() {
return mRingCenterRadius;
}
/**
* @param show {@code true} if should show the arrow head on the progress spinner
*/
void setShowArrow(boolean show) {
if (mShowArrow != show) {
mShowArrow = show;
}
}
boolean getShowArrow() {
return mShowArrow;
}
/**
* @param scale scale of the arrowhead for the spinner
*/
void setArrowScale(float scale) {
if (scale != mArrowScale) {
mArrowScale = scale;
}
}
float getArrowScale() {
return mArrowScale;
}
/**
* @return The amount the progress spinner is currently rotated, between [0..1].
*/
float getStartingRotation() {
return mStartingRotation;
}
/**
* If the start / end trim are offset to begin with, store them so that animation starts
* from that offset.
*/
void storeOriginals() {
mStartingStartTrim = mStartTrim;
mStartingEndTrim = mEndTrim;
mStartingRotation = mRotation;
}
/**
* Reset the progress spinner to default rotation, start and end angles.
*/
void resetOriginals() {
mStartingStartTrim = 0;
mStartingEndTrim = 0;
mStartingRotation = 0;
setStartTrim(0);
setEndTrim(0);
setRotation(0);
}
}
}