54

Assuming I have two colors, and I need to create a real time animation that fastly switches from a color to another.

I tried just to increment the color hexadecimal until I reach the other, but that gave a really bad animation as it showed lots of unrelated colors.

I am using setColorFilter(color, colorfilter) to change the color of an imageview.

Changing the HUE will give me the best visual results? If so, how can I change it for a solid color?

SOLUTION: I solved it by recursively shifting hue

private int hueChange(int c,int deg){
       float[] hsv = new float[3];       //array to store HSV values
       Color.colorToHSV(c,hsv); //get original HSV values of pixel
       hsv[0]=hsv[0]+deg;                //add the shift to the HUE of HSV array
       hsv[0]=hsv[0]%360;                //confines hue to values:[0,360]
       return Color.HSVToColor(Color.alpha(c),hsv);
    }
zed
  • 3,180
  • 3
  • 27
  • 38

12 Answers12

77

Combining @zed's and @Phil's answer gives a nice smooth transition using the ValueAnimator.

final float[] from = new float[3],
              to =   new float[3];

Color.colorToHSV(Color.parseColor("#FFFFFFFF"), from);   // from white
Color.colorToHSV(Color.parseColor("#FFFF0000"), to);     // to red

ValueAnimator anim = ValueAnimator.ofFloat(0, 1);   // animate from 0 to 1
anim.setDuration(300);                              // for 300 ms

final float[] hsv  = new float[3];                  // transition color
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){
    @Override public void onAnimationUpdate(ValueAnimator animation) {
        // Transition along each axis of HSV (hue, saturation, value)
        hsv[0] = from[0] + (to[0] - from[0])*animation.getAnimatedFraction();
        hsv[1] = from[1] + (to[1] - from[1])*animation.getAnimatedFraction();
        hsv[2] = from[2] + (to[2] - from[2])*animation.getAnimatedFraction();

        view.setBackgroundColor(Color.HSVToColor(hsv));
    }
});

anim.start();                                        

The HSV will give a nicer transition than Androids default color space because HSV describes colors in cylindrical coordinates that nicely separate the color's properties and allow a smooth transition across a single axis. You can see from the image below that traveling along the H, S, or V directions gives a nice continuous transition between colors.

enter image description here

damienix
  • 6,463
  • 1
  • 23
  • 30
bcorso
  • 45,608
  • 10
  • 63
  • 75
  • Hi, in case if you're interested, here is a simple class that I made to make this color interpolation look more natural: https://github.com/konmik/animated-color – konmik Oct 31 '15 at 11:13
  • See @dimsuz answer below, it does a lot of this work for you – AlexIIP Apr 26 '16 at 22:11
  • 2
    This actually looks really bad because for example to animate from blue to dark grey this will pass through green, yellow and even some red. – vovahost Jul 18 '17 at 19:55
  • Not sure why this is upvoted. The effect of this is quite bad as like @vovahost says it transitions through other colors to get to the target color. For example, if you transition from green to blue, it's green -> cyan -> blue. For Cyan to Magenta, it's cyan -> blue -> magenta. You can get a better effect by just animating the integer color value from source to target as indicated in dimsuz's answer. – Jeffrey Blattman Nov 14 '17 at 19:23
  • My guess is that I have more upvotes because I answered it a year before the other answer existed ¯\_(ツ)_/¯. I agree, @vovahost's answer is simpler, and probably what most people want. However, this answer is good if you need to manually tweak or customize the transition, and the image helps explain how to do that. – bcorso Nov 16 '17 at 08:35
  • @vovahost the reason why it does that, is because you are not really using the advantage of HSV. If you want to change the saturation, you cannot blend with rgb(100, 100, 100) (gray) since that will most likely translate to hsv(0, 0, 40%) which means it will cycle through the hue. What you instead has to do, is change the saturation from the original color. Here is an example from green to gray: green : rgb(0, 100, 0) hsv(120, 100%,39.22%) green step 50 : rgb(50, 100, 50) hsv(120, 50%, 39.22%) gray : rgb(100, 100, 100) hsv(120, 0%, 39.22%) – Rasive Mar 15 '18 at 12:06
65

You can simply use ArgbEvaluator which is available since API 11 (Honeycomb):

ValueAnimator anim = new ValueAnimator();
anim.setIntValues(color1, color2);
anim.setEvaluator(new ArgbEvaluator());
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        view.setBackgroundColor((Integer)valueAnimator.getAnimatedValue());
    }
});

anim.setDuration(300);
anim.start();

Even better, beginning with API 21 (Lollipop 5.0) you can replace the first 3 lines in the code above with one:

ValueAnimator anim = ValueAnimator.ofArgb(color1, color2)
Codeversed
  • 9,287
  • 3
  • 43
  • 42
dimsuz
  • 8,969
  • 8
  • 54
  • 88
20

You can use a ValueAnimator:

//animate from your current color to red
ValueAnimator anim = ValueAnimator.ofInt(view.getBackgroundColor(), Color.parseColor("#FF0000"));
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        view.setBackgroundColor(animation.getAnimatedValue());
    }
});

anim.start();

You can also set duration or other parameters before calling anim.start(). For example:

anim.setDuration(400);

will set the animation duration to 400ms.


Finally, note that ValueAnimator is available starting in Honeycomb, so if you are supporting older SDKs, you can use NineOldAndroids.

Mohammad Jafar Mashhadi
  • 4,102
  • 3
  • 29
  • 49
Phil
  • 35,852
  • 23
  • 123
  • 164
  • 1
    I don't believe it will give me a beautiful visual effect. I managed to do it by shifting hue. – zed Aug 13 '13 at 19:08
  • 4
    @ZaidDaba'een from my experience, this does provide a very visually appealing effect, however I am glad you found something else that works well for you. – Phil Aug 13 '13 at 19:16
  • @zed will you add your solution as the accepted answer, or accept this one? It would be good to mark this question as answered, since it keeps the site tidy. – Phil May 07 '14 at 15:44
  • 3
    Without ARGB evaluation (as mentioned below) this will just iterate though the color ints, which in most cases will look pretty bad. – Dori Apr 15 '16 at 13:34
  • I think you should replace "ofInt()" with "ofArgb()", and "ofArgb()" requires API 21 and above but it works very well for animation that changes color from A to B. "ofInt()" works very bad for me. – Chanh Oct 03 '19 at 05:03
13

best way use blendARGB in ColorUtils.

esay and minimum line code.

view.setBackgroundColor(ColorUtils.blendARGB(Color.parseColor("#FFFFFF"), Color.parseColor("#CCCCCC"), fraction));
Rasoul Miri
  • 11,234
  • 1
  • 68
  • 78
11

The other answers gave me a weird effect when transitioning very different colors. From yellow to gray it would reach green at some point during the animation.

What worked best for me was the following snippet. This created a really smooth transition with no weird colors appearing in-between.

private void changeViewColor(View view) {
    // Load initial and final colors.
    final int initialColor = getResources().getColor(R.color.some_color);
    final int finalColor = getResources().getColor(R.color.another_color);

    ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // Use animation position to blend colors.
            float position = animation.getAnimatedFraction();
            int blended = blendColors(initialColor, finalColor, position);

            // Apply blended color to the view.
            view.setBackgroundColor(blended);
        }
    });

    anim.setDuration(500).start();
}

private int blendColors(int from, int to, float ratio) {
    final float inverseRatio = 1f - ratio;

    final float r = Color.red(to) * ratio + Color.red(from) * inverseRatio;
    final float g = Color.green(to) * ratio + Color.green(from) * inverseRatio;
    final float b = Color.blue(to) * ratio + Color.blue(from) * inverseRatio;

    return Color.rgb((int) r, (int) g, (int) b);
}
fehbari
  • 1,429
  • 2
  • 16
  • 22
8

You can use TransitionDrawable

TransitionDrawable transition = (TransitionDrawable) viewObj.getBackground();
transition.startTransition(transitionTime);

create xml file in the drawable folder you could write something like:

<transition xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/end_color" />
    <item android:drawable="@drawable/start_color" />
</transition>

Then, in your xml for the actual View you would reference this TransitionDrawable in the android:background attribute.

Muhammad Aamir Ali
  • 20,419
  • 10
  • 66
  • 57
5

The following snippet worked perfectly for me. I thought of using ValueAnimator.ofArgb() but that requires a minimum of api 21. Instead the following works with api 11 and higher. It achieves the color changing smoothly.

ArgbEvaluator evaluator = new ArgbEvaluator();
ValueAnimator animator = new ValueAnimator();
animator.setIntValues(Color.parseColor("#FFFFFF"), Color.parseColor("#000000"));
animator.setEvaluator(evaluator);
animator.setDuration(1000);
//animator.setRepeatCount(ValueAnimator.INFINITE);
//animator.setRepeatMode(ValueAnimator.REVERSE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            color = (int) animation.getAnimatedValue();
            //postInvalidate(); if you are animating a canvas
            //View.setBackgroundColor(color); another exampleof where to use
        }
    });
animator.start();
patelb
  • 2,491
  • 19
  • 18
4

I've found that the implementation used by ArgbEvaluator in the android source code does best job in transitioning colors. When using HSV, depending on the two colors, the transition was jumping through too many hues for me. But this methods doesn't. If you are trying to simply animate use ArgbEvaluator with ValueAnimator as suggested here:

ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() { 

    @Override 
    public void onAnimationUpdate(ValueAnimator animator) {
        view.setBackgroundColor((int) animator.getAnimatedValue());
    } 

}); 
colorAnimation.start(); 

However, if you are like me and want to tie your transition with some user gesture or other value passed from an input, the ValueAnimator is not of much help (unless your are targeting for API 22 and above, in which case you can use the ValueAnimator.setCurrentFraction() method). When targeting below API 22, wrap the code found in ArgbEvaluator source code in your own method, as shown below:

public static int interpolateColor(float fraction, int startValue, int endValue) {
    int startA = (startValue >> 24) & 0xff;
    int startR = (startValue >> 16) & 0xff;
    int startG = (startValue >> 8) & 0xff;
    int startB = startValue & 0xff;
    int endA = (endValue >> 24) & 0xff;
    int endR = (endValue >> 16) & 0xff;
    int endG = (endValue >> 8) & 0xff;
    int endB = endValue & 0xff;
    return ((startA + (int) (fraction * (endA - startA))) << 24) |
            ((startR + (int) (fraction * (endR - startR))) << 16) |
            ((startG + (int) (fraction * (endG - startG))) << 8) |
            ((startB + (int) (fraction * (endB - startB))));
}

and use it however you wish.

Community
  • 1
  • 1
fahmy
  • 3,543
  • 31
  • 47
4

If you like me are trying to change from one RGB color to either white or black, you will find it difficult using @bcorso's answer since it's the wrong color space for that particular job. Not that the answer is wrong, that is not what I am saying.

Here is an example of doing the blending using a color colorA, using white (which can be substituted with black):

float[] from = new float[3];
float[] hsl = new float[3];

ColorUtils.colorToHSL(colorA, from);
float[] to = new float[] {from[0], from[1], 1.0f /* WHITE */};

animator = new ValueAnimator();
animator.setFloatValues(0.0f, 1.0f);
animator.setDuration(1000L);
animator.addUpdateListener(animation -> {
    ColorUtils.blendHSL(from, to, (float) animation.getAnimatedValue(), hsl);

    setColor(ColorUtils.HSLToColor(hsl));
});

There seem to be a lot of confusion about the two, so here is a picture describing it visually. HSL and HSV

Rasive
  • 1,143
  • 1
  • 8
  • 20
3

SOLUTION: I solved it by recursively shifting hue

private int hueChange(int c,int deg){
       float[] hsv = new float[3];       //array to store HSV values
       Color.colorToHSV(c,hsv); //get original HSV values of pixel
       hsv[0]=hsv[0]+deg;                //add the shift to the HUE of HSV array
       hsv[0]=hsv[0]%360;                //confines hue to values:[0,360]
       return Color.HSVToColor(Color.alpha(c),hsv);
    }
zed
  • 3,180
  • 3
  • 27
  • 38
  • 2
    You should use `ValueAnimator` for this type of thing. I've posted an answer on how to do this with HSV values. – bcorso Jul 08 '14 at 21:35
1

I tried @bcorso and @damienix and their solution didn't work for me. The math in this function is much simpler as it follows the basic interpolation formula:

p(t) = (1 - t) * p1 + t * p2, where t [0, 1]

public int interpolateColorFromTo(int currColor, int nextColor, float t) {
    final float[] from = new float[3];
    final float[] to = new float[3];

    Color.colorToHSV(currColor, from);
    Color.colorToHSV(nextColor, to);

    final float[] hsv  = new float[3];
    hsv[0] = (1.0f - t) * from[0] + t * to[0];
    hsv[1] = (1.0f - t) * from[1] + t * to[1];
    hsv[2] = (1.0f - t) * from[2] + t * to[2];

    return Color.HSVToColor(hsv);
}
Karim Fikani
  • 356
  • 3
  • 23
0

First, I would like to thank @bcorso for the great answer and data about HSV.

While I was using the proposed algorithm for some time, I was slightly annoyed by the fact that it makes a color shift from one color to another touching unrelated colors in the process. So, I finally implemented my own stupid simple algorithm that uses real 3D coordinates to make such interpolation:

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

konmik
  • 3,150
  • 19
  • 15