package com.marshalchen.common.uimodule.googleprogressbar;
import android.content.Context;
import android.graphics.*;
import android.graphics.drawable.Drawable;
import com.marshalchen.common.uimodule.R;
/**
* FoldingCirclesDrawable
* User: romainpiel
* Date: 07/04/2014
* Time: 13:00
*/
public class FoldingCirclesDrawable extends Drawable implements Drawable.Callback {
private static final float MAX_LEVEL = 10000;
private static final float CIRCLE_COUNT = ProgressStates.values().length;
private static final float MAX_LEVEL_PER_CIRCLE = MAX_LEVEL / CIRCLE_COUNT;
private static final int ALPHA_OPAQUE = 255;
private static final int ALPHA_ABOVE_DEFAULT = 235;
private Paint mFstHalfPaint;
private Paint mScndHalfPaint;
private Paint mAbovePaint;
private RectF mOval = new RectF();
private int mDiameter;
private Path mPath;
private int mHalf;
private ProgressStates mCurrentState;
private int mControlPointMinimum;
private int mControlPointMaximum;
private int mAxisValue;
private int mAlpha = ALPHA_OPAQUE;
private ColorFilter mColorFilter;
private static int mColor1;
private static int mColor2;
private static int mColor3;
private static int mColor4;
private int fstColor, scndColor;
private boolean goesBackward;
private enum ProgressStates {
FOLDING_DOWN,
FOLDING_LEFT,
FOLDING_UP,
FOLDING_RIGHT
}
public FoldingCirclesDrawable(int[] colors) {
initCirclesProgress(colors);
}
private void initCirclesProgress(int[] colors) {
initColors(colors);
mPath = new Path();
Paint basePaint = new Paint();
basePaint.setAntiAlias(true);
mFstHalfPaint = new Paint(basePaint);
mScndHalfPaint = new Paint(basePaint);
mAbovePaint = new Paint(basePaint);
// init alpha and color filter
setAlpha(mAlpha);
setColorFilter(mColorFilter);
}
private void initColors(int[] colors) {
mColor1=colors[0];
mColor2=colors[1];
mColor3=colors[2];
mColor4=colors[3];
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
measureCircleProgress(bounds.width(), bounds.height());
}
@Override
protected boolean onLevelChange(int level) {
// level goes from 0 to 10000 but the number of colors divides 10000
// so we need to do that hack that maps level 10000 to level 0
int animationLevel = level == MAX_LEVEL ? 0 : level;
// state
int stateForLevel = (int) (animationLevel / MAX_LEVEL_PER_CIRCLE);
mCurrentState = ProgressStates.values()[stateForLevel];
// colors
resetColor(mCurrentState);
int levelForCircle = (int) (animationLevel % MAX_LEVEL_PER_CIRCLE);
boolean halfPassed;
if (!goesBackward) {
halfPassed = levelForCircle != (int) (animationLevel % (MAX_LEVEL_PER_CIRCLE / 2));
} else {
halfPassed = levelForCircle == (int) (animationLevel % (MAX_LEVEL_PER_CIRCLE / 2));
levelForCircle = (int) (MAX_LEVEL_PER_CIRCLE - levelForCircle);
}
mFstHalfPaint.setColor(fstColor);
mScndHalfPaint.setColor(scndColor);
if (!halfPassed) {
mAbovePaint.setColor(mScndHalfPaint.getColor());
} else {
mAbovePaint.setColor(mFstHalfPaint.getColor());
}
// invalidate alpha (Paint#setAlpha is a shortcut for setColor(alpha part)
// so alpha is affected by setColor())
setAlpha(mAlpha);
// axis
mAxisValue = (int) (mControlPointMinimum + (mControlPointMaximum - mControlPointMinimum) * (levelForCircle / MAX_LEVEL_PER_CIRCLE));
return true;
}
private void resetColor(ProgressStates currentState) {
switch (currentState){
case FOLDING_DOWN:
fstColor= mColor1;
scndColor=mColor2;
goesBackward=false;
break;
case FOLDING_LEFT:
fstColor= mColor1;
scndColor=mColor3;
goesBackward=true;
break;
case FOLDING_UP:
fstColor= mColor3;
scndColor=mColor4;
goesBackward=true;
break;
case FOLDING_RIGHT:
fstColor=mColor2;
scndColor=mColor4;
goesBackward=false;
break;
}
}
@Override
public void draw(Canvas canvas) {
if (mCurrentState != null) {
makeCirclesProgress(canvas);
}
}
private void measureCircleProgress(int width, int height) {
mDiameter = Math.min(width, height);
mHalf = mDiameter / 2;
mOval.set(0, 0, mDiameter, mDiameter);
mControlPointMinimum = -mDiameter / 6;
mControlPointMaximum = mDiameter + mDiameter / 6;
}
private void makeCirclesProgress(Canvas canvas) {
switch (mCurrentState) {
case FOLDING_DOWN:
case FOLDING_UP:
drawYMotion(canvas);
break;
case FOLDING_RIGHT:
case FOLDING_LEFT:
drawXMotion(canvas);
break;
}
canvas.drawPath(mPath, mAbovePaint);
}
private void drawXMotion(Canvas canvas) {
canvas.drawArc(mOval, 90, 180, true, mFstHalfPaint);
canvas.drawArc(mOval, -270, -180, true, mScndHalfPaint);
mPath.reset();
mPath.moveTo(mHalf, 0);
mPath.cubicTo(mAxisValue, 0, mAxisValue, mDiameter, mHalf, mDiameter);
}
private void drawYMotion(Canvas canvas) {
canvas.drawArc(mOval, 0, -180, true, mFstHalfPaint);
canvas.drawArc(mOval, -180, -180, true, mScndHalfPaint);
mPath.reset();
mPath.moveTo(0, mHalf);
mPath.cubicTo(0, mAxisValue, mDiameter, mAxisValue, mDiameter, mHalf);
}
@Override
public void setAlpha(int alpha) {
this.mAlpha = alpha;
mFstHalfPaint.setAlpha(alpha);
mScndHalfPaint.setAlpha(alpha);
int targetAboveAlpha = (ALPHA_ABOVE_DEFAULT * alpha) / ALPHA_OPAQUE;
mAbovePaint.setAlpha(targetAboveAlpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
this.mColorFilter = cf;
mFstHalfPaint.setColorFilter(cf);
mScndHalfPaint.setColorFilter(cf);
mAbovePaint.setColorFilter(cf);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void invalidateDrawable(Drawable who) {
final Callback callback = getCallback();
if (callback != null) {
callback.invalidateDrawable(this);
}
}
@Override
public void scheduleDrawable(Drawable who, Runnable what, long when) {
final Callback callback = getCallback();
if (callback != null) {
callback.scheduleDrawable(this, what, when);
}
}
@Override
public void unscheduleDrawable(Drawable who, Runnable what) {
final Callback callback = getCallback();
if (callback != null) {
callback.unscheduleDrawable(this, what);
}
}
public static class Builder {
private int[] mColors;
public Builder(Context context){
initDefaults(context);
}
private void initDefaults(Context context) {
//Default values
mColors = context.getResources().getIntArray(R.array.google_colors);
}
public Builder colors(int[] colors) {
if (colors == null || colors.length == 0) {
throw new IllegalArgumentException("Your color array must contains at least 4 values");
}
mColors = colors;
return this;
}
public Drawable build() {
return new FoldingCirclesDrawable(mColors);
}
}
}