package fr.castorflex.android.smoothprogressbar;
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.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;
/**
* Created by castorflex on 11/10/13.
*/
public class SmoothProgressDrawable extends Drawable implements Animatable {
private static final long FRAME_DURATION = 1000 / 60;
private final static float OFFSET_PER_FRAME = 0.01f;
private Interpolator mInterpolator;
private Rect mBounds;
private Paint mPaint;
private int[] mColors;
private int mColorsIndex;
private boolean mRunning;
private float mCurrentOffset;
private int mSeparatorLength;
private int mSectionsCount;
private float mSpeed;
private boolean mReversed;
private boolean mNewTurn;
private boolean mMirrorMode;
private SmoothProgressDrawable(Interpolator interpolator, int sectionsCount, int separatorLength, int[] colors, int width, float speed, boolean reversed, boolean mirrorMode) {
mRunning = false;
mInterpolator = interpolator;
mSectionsCount = sectionsCount;
mSeparatorLength = separatorLength;
mSpeed = speed;
mReversed = reversed;
mColors = colors;
mColorsIndex = 0;
mMirrorMode = mirrorMode;
mPaint = new Paint();
mPaint.setStrokeWidth(width);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setDither(false);
mPaint.setAntiAlias(false);
}
////////////////////////////////////////////////////////////////////////////
/////////////////// DRAW
@Override
public void draw(Canvas canvas) {
mBounds = getBounds();
canvas.clipRect(mBounds);
int boundsWidth = mBounds.width();
if (mReversed) {
canvas.translate(boundsWidth, 0);
canvas.scale(-1, 1);
}
drawStrokes(canvas);
}
private void drawStrokes(Canvas canvas) {
float prevValue = 0f;
int boundsWidth = mBounds.width();
if (mMirrorMode) boundsWidth /= 2;
int width = boundsWidth + mSeparatorLength + mSectionsCount;
int centerY = mBounds.centerY();
float xSectionWidth = 1f / mSectionsCount;
//new turn
if (mNewTurn) {
mColorsIndex = decrementColor(mColorsIndex);
mNewTurn = false;
}
float prev;
float end;
float spaceLength;
float xOffset;
float ratioSectionWidth;
float sectionWidth;
float drawLength;
int currentIndexColor = mColorsIndex;
for (int i = 0; i <= mSectionsCount; ++i) {
xOffset = xSectionWidth * i + mCurrentOffset;
prev = Math.max(0f, xOffset - xSectionWidth);
ratioSectionWidth = Math.abs(
mInterpolator.getInterpolation(prev) -
mInterpolator.getInterpolation(Math.min(xOffset, 1f)));
sectionWidth = (int) (width * ratioSectionWidth);
if (sectionWidth + prev < width)
spaceLength = Math.min(sectionWidth, mSeparatorLength);
else
spaceLength = 0f;
drawLength = sectionWidth > spaceLength ? sectionWidth - spaceLength : 0;
end = prevValue + drawLength;
if (end > prevValue) {
drawLine(canvas, boundsWidth,
Math.min(boundsWidth, prevValue), centerY, Math.min(boundsWidth, end), centerY,
currentIndexColor);
}
prevValue = end + spaceLength;
currentIndexColor = incrementColor(currentIndexColor);
}
}
private void drawLine(Canvas canvas, int canvasWidth, float startX, float startY, float stopX, float stopY, int currentIndexColor) {
mPaint.setColor(mColors[currentIndexColor]);
if (!mMirrorMode) {
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
} else {
if (mReversed) {
canvas.drawLine(canvasWidth + startX, startY, canvasWidth + stopX, stopY, mPaint);
canvas.drawLine(canvasWidth - startX, startY, canvasWidth - stopX, stopY, mPaint);
} else {
canvas.drawLine(startX, startY, stopX, stopY, mPaint);
canvas.drawLine(canvasWidth * 2 - startX, startY, canvasWidth * 2 - stopX, stopY, mPaint);
}
}
canvas.save();
}
private int incrementColor(int colorIndex) {
++colorIndex;
if (colorIndex >= mColors.length) colorIndex = 0;
return colorIndex;
}
private int decrementColor(int colorIndex) {
--colorIndex;
if (colorIndex < 0) colorIndex = mColors.length - 1;
return colorIndex;
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSPARENT;
}
///////////////////////////////////////////////////////////////////////////
/////////////////// Animation: based on http://cyrilmottier.com/2012/11/27/actionbar-on-the-move/
@Override
public void start() {
if (isRunning()) return;
scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
invalidateSelf();
}
@Override
public void stop() {
if (!isRunning()) return;
mRunning = false;
unscheduleSelf(mUpdater);
}
@Override
public void scheduleSelf(Runnable what, long when) {
mRunning = true;
super.scheduleSelf(what, when);
}
@Override
public boolean isRunning() {
return mRunning;
}
private final Runnable mUpdater = new Runnable() {
@Override
public void run() {
mCurrentOffset += (OFFSET_PER_FRAME * mSpeed);
if (mCurrentOffset >= (1f / mSectionsCount)) {
mNewTurn = true;
mCurrentOffset = 0f;
}
scheduleSelf(mUpdater, SystemClock.uptimeMillis() + FRAME_DURATION);
invalidateSelf();
}
};
////////////////////////////////////////////////////////////////////////////
/////////////////// BUILDER
/**
* Builder for SmoothProgressDrawable! You must use it!
*/
public static class Builder {
private Interpolator mInterpolator;
private int mSectionsCount;
private int[] mColors;
private float mSpeed;
private boolean mReversed;
private boolean mMirrorMode;
private int mStrokeSeparatorLength;
private int mStrokeWidth;
public Builder(Context context) {
initValues(context);
}
public SmoothProgressDrawable build() {
SmoothProgressDrawable ret = new SmoothProgressDrawable(mInterpolator, mSectionsCount, mStrokeSeparatorLength, mColors, mStrokeWidth, mSpeed, mReversed, mMirrorMode);
return ret;
}
private void initValues(Context context) {
Resources res = context.getResources();
mInterpolator = new AccelerateInterpolator();
mSectionsCount = res.getInteger(R.integer.spb_default_sections_count);
mColors = new int[]{res.getColor(R.color.spb_default_color)};
mSpeed = Float.parseFloat(res.getString(R.string.spb_default_speed));
mReversed = res.getBoolean(R.bool.spb_default_reversed);
mStrokeSeparatorLength = res.getDimensionPixelSize(R.dimen.spb_default_stroke_separator_length);
mStrokeWidth = res.getDimensionPixelOffset(R.dimen.spb_default_stroke_width);
}
public Builder interpolator(Interpolator interpolator) {
if (interpolator == null)
throw new IllegalArgumentException("Interpolator can't be null");
mInterpolator = interpolator;
return this;
}
public Builder sectionsCount(int sectionsCount) {
if (sectionsCount <= 0) throw new IllegalArgumentException("SectionsCount must be > 0");
mSectionsCount = sectionsCount;
return this;
}
public Builder separatorLength(int separatorLength) {
if (separatorLength < 0)
throw new IllegalArgumentException("SeparatorLength must be >= 0");
mStrokeSeparatorLength = separatorLength;
return this;
}
public Builder color(int color) {
mColors = new int[]{color};
return this;
}
public Builder colors(int[] colors) {
if (colors == null || colors.length == 0)
throw new IllegalArgumentException("Your color array must not be empty");
mColors = colors;
return this;
}
public Builder width(int width) {
if (width < 0) throw new IllegalArgumentException("The width must be >= 0");
mStrokeWidth = width;
return this;
}
public Builder speed(float speed) {
if (speed < 0) throw new IllegalArgumentException("Speed must be >= 0");
mSpeed = speed;
return this;
}
public Builder reversed(boolean reversed) {
mReversed = reversed;
return this;
}
public Builder mirrorMode(boolean mirrorMode) {
mMirrorMode = mirrorMode;
return this;
}
}
}