package com.marshalchen.common.uimodule.googleprogressbar; import android.content.Context; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import com.marshalchen.ultimateandroiduilollipop.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); } } }