Nightonke/WoWoViewPager

Color animation

Closed this issue · 7 comments

Hi,

I've noticed you have the HSV animation issue.

How about trying this algorithm?

https://github.com/konmik/animated-color

The animation you mentioned is so great. I will update my stupid HSV animation ASAP. Thanks for your notification~

@konmik
Hey, I found that when I try to change color from green(#00ff00) to blue(#0000ff) with your algorithm. The transformation of color is strange. Did I get any mistakes when using the algorithm you provided?
My code in WoWoBackgroundColorAnimation:

public class WoWoBackgroundColorAnimation extends PageAnimation {

    private EaseType easeType;
    private boolean useSameEaseTypeBack = true;

    private ColorChangeType colorChangeType;

    private int targetColor;
    private int fromColor;

    private int targetA = -1;
    private int targetR = -1;
    private int targetG = -1;
    private int targetB = -1;
    private float[] targetHSV = new float[3];
    private int fromA = -1;
    private int fromR = -1;
    private int fromG = -1;
    private int fromB = -1;
    private float[] fromHSV = new float[3];

    /**
     *
     * @param page animation starting page
     * @param startOffset animation starting offset
     * @param endOffset animation ending offset
     * @param fromColor original color
     * @param targetColor target color
     * @param colorChangeType how to change the color.
     *                        For more information, please check the ColorChangeType.class
     * @param easeType ease type.
     *                 For more information, please check the EaseType.class
     * @param useSameEaseTypeBack whether use the same ease type to back
     */
    public WoWoBackgroundColorAnimation(
            int page,
            float startOffset,
            float endOffset,
            int fromColor,
            int targetColor,
            ColorChangeType colorChangeType,
            EaseType easeType,
            boolean useSameEaseTypeBack) {

        setPage(page);
        setStartOffset(startOffset);
        setEndOffset(endOffset);

        this.easeType = easeType;
        this.useSameEaseTypeBack = useSameEaseTypeBack;
        this.fromColor = fromColor;
        this.targetColor = targetColor;
        setARGBandHSV();

        this.colorChangeType = colorChangeType;
    }

    private float lastPositionOffset = -1;

    private boolean lastTimeIsExceed = false;
    private boolean lastTimeIsLess = false;

    @Override
    public void play(View onView, float positionOffset) {

        // if the positionOffset is less than the starting color,
        // we should set onView to starting color
        // otherwise there may be offsets between starting color and actually color
        // if the last time we do this, just return
        if (positionOffset <= getStartOffset()) {
            if (lastTimeIsLess) return;
            onView.setBackgroundColor(fromColor);
            lastTimeIsLess = true;
            return;
        }
        lastTimeIsLess = false;

        // if the positionOffset exceeds the endOffset,
        // we should set onView to target color
        // otherwise there may be offsets between target color and actually color
        // if the last time we do this, just return
        if (positionOffset >= getEndOffset()) {
            if (lastTimeIsExceed) return;
            onView.setBackgroundColor(targetColor);
            lastTimeIsExceed = true;
            return;
        }
        lastTimeIsExceed = false;

        // get the true offset
        positionOffset = (positionOffset - getStartOffset()) / (getEndOffset() - getStartOffset());
        float movementOffset;

        if (lastPositionOffset == -1) {
            // first movement
            movementOffset = easeType.getOffset(positionOffset);
        } else {
            if (positionOffset < lastPositionOffset) {
                // back
                if (useSameEaseTypeBack) {
                    movementOffset = 1 - easeType.getOffset(1 - positionOffset);
                } else {
                    movementOffset = easeType.getOffset(positionOffset);
                }
            } else {
                // forward
                movementOffset = easeType.getOffset(positionOffset);
            }
        }
        lastPositionOffset = positionOffset;

        if (colorChangeType == ColorChangeType.RGB) {
            onView.setBackgroundColor(
                    Color.argb(
                            fromA + (int)((targetA - fromA) * movementOffset),
                            fromR + (int)((targetR - fromR) * movementOffset),
                            fromG + (int)((targetG - fromG) * movementOffset),
                            fromB + (int)((targetB - fromB) * movementOffset))
            );
        } else {
//            onView.setBackgroundColor(Color.HSVToColor(new float[]{
//                    fromHSV[0] + (targetHSV[0] - fromHSV[0]) * movementOffset,
//                    fromHSV[1] + (targetHSV[1] - fromHSV[1]) * movementOffset,
//                    fromHSV[2] + (targetHSV[2] - fromHSV[2]) * movementOffset
//            }));
            onView.setBackgroundColor(Color.HSVToColor(toHSV(move(vector0, vector1, movementOffset))));
        }
    }

    private void setARGBandHSV() {
        targetA = Color.alpha(targetColor);
        targetR = Color.red(targetColor);
        targetG = Color.green(targetColor);
        targetB = Color.blue(targetColor);
        Color.colorToHSV(targetColor, targetHSV);

        fromA = Color.alpha(fromColor);
        fromR = Color.red(fromColor);
        fromG = Color.green(fromColor);
        fromB = Color.blue(fromColor);
        Color.colorToHSV(fromColor, fromHSV);

        this.vector0 = toVector(toHSV(fromColor));
        this.vector1 = toVector(toHSV(targetColor));
    }

    private static final float ERROR = 0.001f;
    private float[] vector0 = null;
    private float[] vector1 = null;

    public int with(float delta) {
        if (delta <= 0)
            return fromColor;
        if (delta >= 1)
            return targetColor;
        return Color.HSVToColor(toHSV(move(vector0, vector1, delta)));
    }

    public static float[] move(float[] vector0, float[] vector1, float delta) {
        float[] vector = new float[3];
        vector[0] = (vector1[0] - vector0[0]) * delta + vector0[0];
        vector[1] = (vector1[1] - vector0[1]) * delta + vector0[1];
        vector[2] = (vector1[2] - vector0[2]) * delta + vector0[2];
        return vector;
    }

    public static float[] toHSV(int color) {
        float[] hsv = new float[3];
        Color.colorToHSV(color, hsv);
        return hsv;
    }

    public static float[] toVector(float[] hsv) {
        float[] vector = new float[3];
        double rad = Math.PI * hsv[0] / 180;
        vector[0] = (float) Math.cos(rad) * hsv[1];
        vector[1] = (float) Math.sin(rad) * hsv[1];
        vector[2] = hsv[2];
        return vector;
    }

    public static float[] toHSV(float[] vector) {
        float[] hsv = new float[3];
        hsv[1] = (float) Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1]);
        hsv[0] = hsv[1] < ERROR ? 0 :
                (float) (Math.atan2(vector[1] / hsv[1], vector[0] / hsv[1]) * 180 / Math.PI);
        hsv[2] = vector[2];
        return hsv;
    }
}

I just copied your algorithm behind the original codes.
The effect:
new_hsv_algorithm_error
The color will change to red, and suddenly to blue.

Oh, I will try this today, thanks! :D

It looks that there is a bug somewhere in robolectric. I'll release a fix soon.

Android's HSVToColor does not work with negative hue values.

Here is the fix:

https://github.com/konmik/animated-color/blob/master/animated-color/src/main/java/info/android15/color3d/AnimatedColor.java#L67-L68

Thank you for trying this algorithm! :) I think there is a way to improve it even more - I could decrease value while transforming from color to color to compensate the color intensity while it moves across the center.

Yep, good idea. And thanks for the rapid fix. I will try this later.

@konmik I have publish a new version which uses your better HSV animation. Thank you for the animation!