package it.angelic.soulissclient.util;
import android.graphics.Color;
import android.support.annotation.ColorInt;
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.Size;
/**
* Created by Dimitry Ivanov on 27.10.2015.
*/
public abstract class CCFAnimator {
public interface OnNewColorListener {
void onNewColor(@ColorInt int color);
}
/**
* Concats specified array of {@link CCFAnimator} into a {@link ConcatAnimator}. Maybe be used to
* mix hsv, arg, argb CCFAnimators
*
* @param animators array of {@link CCFAnimator} to include in returned {@link ConcatAnimator}
* @return {@link CCFAnimator}
* @see #hsv(int[])
* @see #rgb(int[])
* @see #argb(int[])
*/
public static CCFAnimator concat(final CCFAnimator... animators) {
return new ConcatAnimator(animators);
}
/**
* Creates a {@link CCFAnimator} animate alpha of specified color
*
* @param color starting color
* @param toAlpha alpha amount to animate specified color
* @return {@link CCFAnimator}
* @see #argb(int, int)
*/
public static CCFAnimator alpha(
@ColorInt int color,
@ColorInt int toAlpha
) {
return argb(color, applyAlpha(color, toAlpha));
}
/**
* Creates a {@link CCFAnimator} to animate `fromColor` color to `toColor`. Alpha property will be ignored
*
* @param fromColor starting color
* @param toColor end color
* @return {@link CCFAnimator}
* @see #argb(int, int)
* @see #rgb(int[])
*/
public static CCFAnimator rgb(
@ColorInt int fromColor,
@ColorInt int toColor
) {
return new RGBAnimator(null, fromColor, toColor);
}
/**
* Creates a {@link CCFAnimator} to animate between array of colors.
* For each pair of colors RGB CCFAnimator will be created
*
* @param colors to animate
* @return {@link CCFAnimator}
* @see #rgb(int, int)
*/
public static CCFAnimator rgb(@Size(min = 2) int[] colors) {
final CCFAnimator[] animators = new CCFAnimator[colors.length - 1];
for (int i = 0, length = animators.length; i < length; i++) {
animators[i] = CCFAnimator.rgb(colors[i], colors[i + 1]);
}
return concat(animators);
}
/**
* Creates a {@link CCFAnimator} to animate `fromColor` color to `toColor`
*
* @param fromColor starting color
* @param toColor end color
* @return {@link CCFAnimator}
* @see #rgb(int, int)
* @see #argb(int[])
*/
public static CCFAnimator argb(
@ColorInt int fromColor,
@ColorInt int toColor
) {
final int fromAlpha = extractAlpha(fromColor);
final int toAlpha = extractAlpha(toColor);
final AlphaEvaluator alphaEvaluator;
if (fromAlpha != toAlpha) {
alphaEvaluator = new AlphaEvaluatorImpl(fromAlpha, toAlpha);
} else {
alphaEvaluator = null;
}
return new RGBAnimator(alphaEvaluator, fromColor, toColor);
}
/**
* Constructs a {@link CCFAnimator} from specified array of colors
*
* @param colors colors to cross-fade (minimum length is 2)
* @return {@link CCFAnimator}
* @see #argb(int, int)
*/
public static CCFAnimator argb(@Size(min = 2) int[] colors) {
final CCFAnimator[] animators = new CCFAnimator[colors.length - 1];
for (int i = 0, length = animators.length; i < length; i++) {
animators[i] = CCFAnimator.argb(colors[i], colors[i + 1]);
}
return concat(animators);
}
/**
* Creates a {@link CCFAnimator} to animate HSV of specified colors. Alpha property will be ignored
*
* @param fromColor starting color
* @param toColor end color
* @return {@link CCFAnimator}
* @see #hsv(int, int, int, int)
* @see #hsv(int[])
*/
public static CCFAnimator hsv(
@ColorInt int fromColor,
@ColorInt int toColor
) {
return hsv(fromColor, toColor, 0, 0);
}
/**
* Creates a {@link CCFAnimator} to animate HSV of specified colors
*
* @param fromColor starting color
* @param toColor end color
* @param fromAlpha start alpha
* @param toAlpha end alpha
* @return {@link CCFAnimator}
* @see #hsv(int, int)
* @see #hsv(int[])
*/
public static CCFAnimator hsv(
@ColorInt int fromColor,
@ColorInt int toColor,
@IntRange(from = 0, to = 255) int fromAlpha,
@IntRange(from = 0, to = 255) int toAlpha
) {
final AlphaEvaluator alphaEvaluator;
if (fromAlpha != toAlpha) {
alphaEvaluator = new AlphaEvaluatorImpl(fromAlpha, toAlpha);
} else {
alphaEvaluator = null;
}
final float[] from = buildHSV(fromColor);
final float[] to = buildHSV(toColor);
// determine whether we are backwards
if (isHSVBackwards(from[0], to[0])) {
return new HSVBackwardsAnimator(alphaEvaluator, fromColor, toColor, from, to);
}
return new HSVAnimator(alphaEvaluator, fromColor, toColor, from, to);
}
/**
* Constructs a {@link CCFAnimator} from specified array of colors
*
* @param colors colors to animate
* @return {@link CCFAnimator}
* @see #hsv(int, int)
* @see #hsv(int, int, int, int)
*/
public static CCFAnimator hsv(@Size(min = 2) int[] colors) {
final CCFAnimator[] animators = new CCFAnimator[colors.length - 1];
for (int i = 0, length = animators.length; i < length; i++) {
animators[i] = CCFAnimator.hsv(colors[i], colors[i + 1]);
}
return concat(animators);
}
protected static boolean isHSVBackwards(float fromH, float toH) {
return Math.abs(toH - fromH) > 180.F;
}
protected static float[] buildHSV(@ColorInt int color) {
final float[] hsv = new float[3];
Color.colorToHSV(color, hsv);
return hsv;
}
protected static int[] buildRGB(@ColorInt int color) {
final int[] rgb = new int[3];
rgb[0] = (color >> 16) & 0xFF;
rgb[1] = (color >> 8) & 0xFF;
rgb[2] = color & 0xFF;
return rgb;
}
@ColorInt
protected static int rgbToColor(int[] rgb) {
return (0xFF << 24) | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}
@ColorInt
protected static int argbToColor(int alpha, int[] rgb) {
return (alpha << 24) | (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
}
@ColorInt
protected static int applyAlpha(int color, int alpha) {
return (color & 0x00FFFFFF) | (alpha << 24);
}
protected static int extractAlpha(@ColorInt int color) {
return color >>> 24;
}
protected CCFAnimator(int fromColor, int toColor) {
}
/**
* Returns a color depending on fraction
*
* @param fraction current animation fraction
* @return color
*/
public abstract int getColor(@FloatRange(from = .0F, to = 1.F) float fraction);
/* public ValueAnimator asValueAnimator(@NonNull final OnNewColorListener onNewColorListener) {
final ValueAnimator animator = ValueAnimator.ofFloat(.0F, 1.F);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float fraction = animation.getAnimatedFraction();
onNewColorListener.onNewColor(getColor(fraction));
}
});
return animator;
}*/
protected interface AlphaEvaluator {
int evaluate(float fraction);
}
protected static class AlphaEvaluatorImpl implements AlphaEvaluator {
private final int mFromAlpha;
private final int mToAlpha;
protected AlphaEvaluatorImpl(int fromAlpha, int toAlpha) {
mFromAlpha = fromAlpha;
mToAlpha = toAlpha;
}
@Override
public int evaluate(float fraction) {
return (int) (mFromAlpha + (mToAlpha - mFromAlpha) * fraction + .5F);
}
}
protected static class RGBAnimator extends CCFAnimator {
private final AlphaEvaluator mAlphaEvaluator;
private final int[] mFromColor;
private final int[] mToColor;
private final int[] mOut;
protected RGBAnimator(
@Nullable AlphaEvaluator alphaEvaluator,
@ColorInt int fromColor,
@ColorInt int toColor
) {
super(fromColor, toColor);
this.mAlphaEvaluator = alphaEvaluator;
this.mFromColor = buildRGB(fromColor);
this.mToColor = buildRGB(toColor);
this.mOut = new int[3];
}
@Override
public int getColor(float fraction) {
mOut[0] = (int) (mFromColor[0] + ((mToColor[0] - mFromColor[0]) * fraction + .5F));
mOut[1] = (int) (mFromColor[1] + ((mToColor[1] - mFromColor[1]) * fraction + .5F));
mOut[2] = (int) (mFromColor[2] + ((mToColor[2] - mFromColor[2]) * fraction + .5F));
if (mAlphaEvaluator != null) {
return argbToColor(mAlphaEvaluator.evaluate(fraction), mOut);
}
return rgbToColor(mOut);
}
}
protected abstract static class AbsHSVAnimator extends CCFAnimator {
private final AlphaEvaluator mAlphaEvaluator;
private final float[] mFrom;
private final float[] mTo;
private final float[] mOut;
protected AbsHSVAnimator(
@Nullable AlphaEvaluator alphaEvaluator,
@ColorInt int fromColor,
@ColorInt int toColor,
@Size(3) float[] fromHSV,
@Size(3) float[] toHSV
) {
super(fromColor, toColor);
this.mAlphaEvaluator = alphaEvaluator;
this.mFrom = fromHSV;
this.mTo = toHSV;
this.mOut = new float[3];
}
@Override
public int getColor(float fraction) {
mOut[0] = getHue(fraction);
mOut[1] = mFrom[1] + ((mTo[1] - mFrom[1]) * fraction);
mOut[2] = mFrom[2] + ((mTo[2] - mFrom[2]) * fraction);
if (mAlphaEvaluator != null) {
return Color.HSVToColor(mAlphaEvaluator.evaluate(fraction), mOut);
}
return Color.HSVToColor(mOut);
}
protected abstract float getHue(float fraction);
}
protected static class HSVAnimator extends AbsHSVAnimator {
private final float mFromH;
private final float mDiff;
protected HSVAnimator(AlphaEvaluator alphaEvaluator, int fromColor, int toColor, float[] fromHSV, float[] toHSV) {
super(alphaEvaluator, fromColor, toColor, fromHSV, toHSV);
mFromH = fromHSV[0];
mDiff = toHSV[0] - fromHSV[0];
}
@Override
protected float getHue(float fraction) {
return mFromH + (mDiff * fraction);
}
}
protected static class HSVBackwardsAnimator extends AbsHSVAnimator {
private final float mFromH;
private final float mDiff;
private final boolean mFromIsBigger;
protected HSVBackwardsAnimator(AlphaEvaluator alphaEvaluator, int fromColor, int toColor, float[] fromHSV, float[] toHSV) {
super(alphaEvaluator, fromColor, toColor, fromHSV, toHSV);
mFromH = fromHSV[0];
mDiff = 360.F - (Math.abs(toHSV[0] - fromHSV[0]));
mFromIsBigger = Float.compare(mFromH, toHSV[0]) > 0;
}
@Override
protected float getHue(float fraction) {
final float evaluated = mDiff * fraction;
if (mFromIsBigger) {
final float left = mFromH + evaluated;
if (Float.compare(left, 360.F) > 0) {
return left - 360.F;
}
return left;
}
final float left = mFromH - evaluated;
if (Float.compare(left, .0F) < 0) {
return 360.F - Math.abs(left);
}
return left;
}
}
protected static class ConcatAnimator extends CCFAnimator {
private final CCFAnimator[] mAnimators;
private final int mLength;
private final float mFractionStep;
protected ConcatAnimator(@NonNull CCFAnimator[] animators) {
super(0, 0);
this.mAnimators = animators;
this.mLength = mAnimators.length;
this.mFractionStep = 1.F / mLength;
}
@Override
public int getColor(float fraction) {
final int index;
final float stepFraction;
{
final int i = (int) (fraction / mFractionStep);
if (i >= mLength) {
index = mLength - 1;
stepFraction = 1.F;
} else {
index = i;
stepFraction = (fraction % mFractionStep) / mFractionStep;
}
}
return mAnimators[index].getColor(stepFraction);
}
}
}