package at.grabner.circleprogress;
import android.animation.TimeInterpolator;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import java.lang.ref.WeakReference;
public class AnimationHandler extends Handler {
private final WeakReference<CircleProgressView> mCircleViewWeakReference;
// Spin bar length in degree at start of animation
private float mSpinningBarLengthStart;
private long mAnimationStartTime;
private long mLengthChangeAnimationStartTime;
private TimeInterpolator mLengthChangeInterpolator = new DecelerateInterpolator();
// The interpolator for value animations
private TimeInterpolator mInterpolator = new AccelerateDecelerateInterpolator();
private double mLengthChangeAnimationDuration;
private long mFrameStartTime = 0;
AnimationHandler(CircleProgressView circleView) {
super(circleView.getContext().getMainLooper());
mCircleViewWeakReference = new WeakReference<CircleProgressView>(circleView);
}
/**
* Sets interpolator for value animations.
*
* @param mInterpolator the m interpolator
*/
public void setValueInterpolator(TimeInterpolator mInterpolator) {
this.mInterpolator = mInterpolator;
}
/**
* Sets the interpolator for length changes of the bar.
*
* @param mLengthChangeInterpolator the m length change interpolator
*/
public void setLengthChangeInterpolator(TimeInterpolator mLengthChangeInterpolator) {
this.mLengthChangeInterpolator = mLengthChangeInterpolator;
}
@Override
public void handleMessage(Message msg) {
CircleProgressView circleView = mCircleViewWeakReference.get();
if (circleView == null) {
return;
}
AnimationMsg msgType = AnimationMsg.values()[msg.what];
if (msgType == AnimationMsg.TICK) {
removeMessages(AnimationMsg.TICK.ordinal()); // necessary to remove concurrent ticks.
}
//if (msgType != AnimationMsg.TICK)
// Log.d("JaGr", TAG + "LOG00099: State:" + circleView.mAnimationState + " Received: " + msgType);
mFrameStartTime = SystemClock.uptimeMillis();
switch (circleView.mAnimationState) {
case IDLE:
switch (msgType) {
case START_SPINNING:
enterSpinning(circleView);
break;
case STOP_SPINNING:
//IGNORE not spinning
break;
case SET_VALUE:
setValue(msg, circleView);
break;
case SET_VALUE_ANIMATED:
enterSetValueAnimated(msg, circleView);
break;
case TICK:
removeMessages(AnimationMsg.TICK.ordinal()); // remove old ticks
//IGNORE nothing to do
break;
}
break;
case SPINNING:
switch (msgType) {
case START_SPINNING:
//IGNORE already spinning
break;
case STOP_SPINNING:
enterEndSpinning(circleView);
break;
case SET_VALUE:
setValue(msg, circleView);
break;
case SET_VALUE_ANIMATED:
enterEndSpinningStartAnimating(circleView, msg);
break;
case TICK:
// set length
float length_delta = circleView.mSpinningBarLengthCurrent - circleView.mSpinningBarLengthOrig;
float t = (float) ((System.currentTimeMillis() - mLengthChangeAnimationStartTime)
/ mLengthChangeAnimationDuration);
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = mLengthChangeInterpolator.getInterpolation(t);
if (Math.abs(length_delta) < 1) {
//spinner length is within bounds
circleView.mSpinningBarLengthCurrent = circleView.mSpinningBarLengthOrig;
} else if (circleView.mSpinningBarLengthCurrent < circleView.mSpinningBarLengthOrig) {
//spinner to short, --> grow
circleView.mSpinningBarLengthCurrent = mSpinningBarLengthStart + ((circleView.mSpinningBarLengthOrig - mSpinningBarLengthStart) * interpolatedRatio);
} else {
//spinner to long, --> shrink
circleView.mSpinningBarLengthCurrent = (mSpinningBarLengthStart - ((mSpinningBarLengthStart - circleView.mSpinningBarLengthOrig) * interpolatedRatio));
}
circleView.mCurrentSpinnerDegreeValue += circleView.mSpinSpeed; // spin speed value (in degree)
if (circleView.mCurrentSpinnerDegreeValue > 360) {
circleView.mCurrentSpinnerDegreeValue = 0;
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
circleView.invalidate();
break;
}
break;
case END_SPINNING:
switch (msgType) {
case START_SPINNING:
circleView.mAnimationState = AnimationState.SPINNING;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
break;
case STOP_SPINNING:
//IGNORE already stopping
break;
case SET_VALUE:
setValue(msg, circleView);
break;
case SET_VALUE_ANIMATED:
enterEndSpinningStartAnimating(circleView, msg);
break;
case TICK:
float t = (float) ((System.currentTimeMillis() - mLengthChangeAnimationStartTime)
/ mLengthChangeAnimationDuration);
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = mLengthChangeInterpolator.getInterpolation(t);
circleView.mSpinningBarLengthCurrent = (mSpinningBarLengthStart) * (1f - interpolatedRatio);
circleView.mCurrentSpinnerDegreeValue += circleView.mSpinSpeed; // spin speed value (not in percent)
if (circleView.mSpinningBarLengthCurrent < 0.01f) {
//end here, spinning finished
circleView.mAnimationState = AnimationState.IDLE;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
circleView.invalidate();
break;
}
break;
case END_SPINNING_START_ANIMATING:
switch (msgType) {
case START_SPINNING:
circleView.mDrawBarWhileSpinning = false;
enterSpinning(circleView);
break;
case STOP_SPINNING:
//IGNORE already stopping
break;
case SET_VALUE:
circleView.mDrawBarWhileSpinning = false;
setValue(msg, circleView);
break;
case SET_VALUE_ANIMATED:
circleView.mValueFrom = 0; // start from zero after spinning
circleView.mValueTo = ((float[]) msg.obj)[1];
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
break;
case TICK:
//shrink spinner till it has its original length
if (circleView.mSpinningBarLengthCurrent > circleView.mSpinningBarLengthOrig && !circleView.mDrawBarWhileSpinning) {
//spinner to long, --> shrink
float t = (float) ((System.currentTimeMillis() - mLengthChangeAnimationStartTime)
/ mLengthChangeAnimationDuration);
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = mLengthChangeInterpolator.getInterpolation(t);
circleView.mSpinningBarLengthCurrent = (mSpinningBarLengthStart) * (1f - interpolatedRatio);
}
// move spinner for spin speed value (not in percent)
circleView.mCurrentSpinnerDegreeValue += circleView.mSpinSpeed;
//if the start of the spinner reaches zero, start animating the value
if (circleView.mCurrentSpinnerDegreeValue > 360 && !circleView.mDrawBarWhileSpinning) {
mAnimationStartTime = System.currentTimeMillis();
circleView.mDrawBarWhileSpinning = true;
initReduceAnimation(circleView);
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(AnimationState.START_ANIMATING_AFTER_SPINNING);
}
}
//value is already animating, calc animation value and reduce spinner
if (circleView.mDrawBarWhileSpinning) {
circleView.mCurrentSpinnerDegreeValue = 360;
circleView.mSpinningBarLengthCurrent -= circleView.mSpinSpeed;
calcNextAnimationValue(circleView);
float t = (float) ((System.currentTimeMillis() - mLengthChangeAnimationStartTime)
/ mLengthChangeAnimationDuration);
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = mLengthChangeInterpolator.getInterpolation(t);
circleView.mSpinningBarLengthCurrent = (mSpinningBarLengthStart) * (1f - interpolatedRatio);
}
//spinner is no longer visible switch state to animating
if (circleView.mSpinningBarLengthCurrent < 0.1) {
//spinning finished, start animating the current value
circleView.mAnimationState = AnimationState.ANIMATING;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
circleView.invalidate();
circleView.mDrawBarWhileSpinning = false;
circleView.mSpinningBarLengthCurrent = circleView.mSpinningBarLengthOrig;
} else {
circleView.invalidate();
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
break;
}
break;
case ANIMATING:
switch (msgType) {
case START_SPINNING:
enterSpinning(circleView);
break;
case STOP_SPINNING:
//Ignore, not spinning
break;
case SET_VALUE:
setValue(msg, circleView);
break;
case SET_VALUE_ANIMATED:
mAnimationStartTime = System.currentTimeMillis();
//restart animation from current value
circleView.mValueFrom = circleView.mCurrentValue;
circleView.mValueTo = ((float[]) msg.obj)[1];
break;
case TICK:
if (calcNextAnimationValue(circleView)) {
//animation finished
circleView.mAnimationState = AnimationState.IDLE;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
circleView.mCurrentValue = circleView.mValueTo;
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
circleView.invalidate();
break;
}
break;
}
}
private void enterSetValueAnimated(Message msg, CircleProgressView circleView) {
circleView.mValueFrom = ((float[]) msg.obj)[0];
circleView.mValueTo = ((float[]) msg.obj)[1];
mAnimationStartTime = System.currentTimeMillis();
circleView.mAnimationState = AnimationState.ANIMATING;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
}
private void enterEndSpinningStartAnimating(CircleProgressView circleView, Message msg) {
circleView.mAnimationState = AnimationState.END_SPINNING_START_ANIMATING;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
circleView.mValueFrom = 0; // start from zero after spinning
circleView.mValueTo = ((float[]) msg.obj)[1];
mLengthChangeAnimationStartTime = System.currentTimeMillis();
mSpinningBarLengthStart = circleView.mSpinningBarLengthCurrent;
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
}
private void enterEndSpinning(CircleProgressView circleView) {
circleView.mAnimationState = AnimationState.END_SPINNING;
initReduceAnimation(circleView);
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
}
private void initReduceAnimation(CircleProgressView circleView) {
float degreesTillFinish = circleView.mSpinningBarLengthCurrent;
float stepsTillFinish = degreesTillFinish / circleView.mSpinSpeed;
mLengthChangeAnimationDuration = (stepsTillFinish * circleView.mFrameDelayMillis) * 2f;
mLengthChangeAnimationStartTime = System.currentTimeMillis();
mSpinningBarLengthStart = circleView.mSpinningBarLengthCurrent;
}
private void enterSpinning(CircleProgressView circleView) {
circleView.mAnimationState = AnimationState.SPINNING;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
circleView.mSpinningBarLengthCurrent = (360f / circleView.mMaxValue * circleView.mCurrentValue);
circleView.mCurrentSpinnerDegreeValue = (360f / circleView.mMaxValue * circleView.mCurrentValue);
mLengthChangeAnimationStartTime = System.currentTimeMillis();
mSpinningBarLengthStart = circleView.mSpinningBarLengthCurrent;
//calc animation time
float stepsTillFinish = circleView.mSpinningBarLengthOrig / circleView.mSpinSpeed;
mLengthChangeAnimationDuration = ((stepsTillFinish * circleView.mFrameDelayMillis) * 2f);
sendEmptyMessageDelayed(AnimationMsg.TICK.ordinal(), circleView.mFrameDelayMillis - (SystemClock.uptimeMillis() - mFrameStartTime));
}
/**
* *
*
* @param circleView the circle view
* @return false if animation still running, true if animation is finished.
*/
private boolean calcNextAnimationValue(CircleProgressView circleView) {
float t = (float) ((System.currentTimeMillis() - mAnimationStartTime)
/ circleView.mAnimationDuration);
t = t > 1.0f ? 1.0f : t;
float interpolatedRatio = mInterpolator.getInterpolation(t);
circleView.mCurrentValue = (circleView.mValueFrom + ((circleView.mValueTo - circleView.mValueFrom) * interpolatedRatio));
return t >= 1;
}
private void setValue(Message msg, CircleProgressView circleView) {
circleView.mValueFrom = circleView.mValueTo;
circleView.mCurrentValue = circleView.mValueTo = ((float[]) msg.obj)[0];
circleView.mAnimationState = AnimationState.IDLE;
if (circleView.mAnimationStateChangedListener != null) {
circleView.mAnimationStateChangedListener.onAnimationStateChanged(circleView.mAnimationState);
}
circleView.invalidate();
}
}