Android:从一种颜色变化到另一种颜色的动画

54

假设我有两种颜色,并且我需要创建一个实时动画,快速地从一种颜色切换到另一种颜色。

我尝试通过逐渐增加十六进制颜色值来达到目的,但这会导致非常糟糕的动画效果,因为会出现很多无关的中间颜色。

我正在使用setColorFilter(color, colorfilter)来更改imageview的颜色。

改变色相是否能给我最佳的视觉效果?如果是这样,如何更改纯色的色相?

解决方案:
我通过递归移动色相来解决了这个问题。

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);
    }

你试过TransitionDrawable吗? - pskink
什么的颜色?ImageView的背景? - Igor
ImageView的ColorFilter颜色。 不,我没有使用TransitionDrawable,因为Drawable既不是固定的,颜色也不是固定的。 - zed
你的意思是:deawable不是固定的? - pskink
使用偏移量混合颜色,请参见我的答案:https://dev59.com/8G025IYBdhLWcg3wVkYR#24285364 - Marqs
你可以参考这个答案来实现颜色动画:https://dev59.com/r3E85IYBdhLWcg3wx2n2#34429554 - RBK
12个回答

77

结合@zed和@Phil的答案,可以使用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();                                        

HSV模式相比安卓默认颜色空间会提供更加平滑的过渡,因为HSV以圆柱坐标描述颜色,很好地分离了颜色的属性,并允许在单个轴上平滑过渡。从下面的图像可以看出,沿着H、S或V方向行进可以在颜色之间获得良好的连续过渡。

enter image description here


嗨,如果你有兴趣的话,这里是我写的一个简单类,可以使颜色插值看起来更自然:https://github.com/konmik/animated-color - konmik
请参考下面 @dimsuz 的回答,它可以为您完成大部分工作。 - AlexIIP
2
这看起来实际上非常糟糕,因为例如从蓝色动画到深灰色,它会经过绿色、黄色甚至一些红色。 - vovahost
不确定为什么这个被点赞了。正如@vovahost所说,这样做的效果相当糟糕,因为它会通过其他颜色进行过渡以达到目标颜色。例如,如果你从绿色过渡到蓝色,它会是绿色->青色->蓝色。对于青色到品红色,它是青色->蓝色->品红色。你可以通过只动画整数颜色值从源到目标来获得更好的效果,就像dimsuz的答案中所示。 - Jeffrey Blattman
我的猜测是我有更多的赞同,因为我在另一个答案存在之前一年就回答了它 ¯_(ツ)_/¯。我同意,@vovahost的答案更简单,可能是大多数人想要的。然而,如果您需要手动调整或自定义转换,则此答案很好,并且图像有助于解释如何执行该操作。 - bcorso
@vovahost之所以出现这种情况,是因为您没有充分利用HSV的优势。如果您想要改变饱和度,就不能与rgb(100, 100, 100)(灰色)混合,因为这很可能会被转换为hsv(0, 0, 40%),这意味着它将穿过色相。相反,您必须从原始颜色开始改变饱和度。以下是从绿色到灰色的示例:绿色:rgb(0, 100, 0) hsv(120, 100%,39.22%) 50步绿色:rgb(50, 100, 50) hsv(120, 50%,39.22%) 灰色:rgb(100, 100, 100) hsv(120, 0%,39.22%) - Rasive

65

您可以简单地使用ArgbEvaluator,它自API 11(蜂巢)以来就可用:

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();

更好的是,从API 21(棒棒糖5.0)开始,您可以将上面代码的前三行替换为一行:

ValueAnimator anim = ValueAnimator.ofArgb(color1, color2)

我发现这在这种情况下工作得很好。谢谢。 - nckbrz
如果您无法依赖API 21来利用ofArgb方法,那么这将非常有用。谢谢! - Michael Krause
很奇怪的是,在过渡期间我经历了严重的闪烁,除非我使用这两种颜色:anim.setIntValues(Color.White,Color.yellow)。有什么想法吗? - knutella
2
@knutella 在 setIntValues(...) 之后调用 setEvaluator(...) 可以防止闪烁。 - Thomas Vos
1
@ThomasVos 谢谢您的回复。现在可以工作了! - knutella

20
你可以使用 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();

在调用 anim.start() 之前,您还可以设置持续时间或其他参数。例如:

anim.setDuration(400);

将动画持续时间设置为400毫秒。


最后请注意,ValueAnimatorHoneycomb 开始可用,因此如果您支持旧的 SDK,可以使用NineOldAndroids


1
我不相信它会给我一个美丽的视觉效果。我通过改变色调来实现了它。 - zed
4
根据我的经验,这确实会产生非常视觉上令人愉悦的效果,但我很高兴你找到了其他适合你的方法。 - Phil
@zed,你会把你的解决方案添加为被接受的答案,还是接受这个答案?将此问题标记为已回答会使网站保持整洁。 - Phil
3
如果没有ARGB评估(如下所述),这只会迭代颜色int,而在大多数情况下,这看起来会非常糟糕。 - Dori
我认为你应该用"ofArgb()"代替"ofInt()",而且"ofArgb()"需要API 21及以上版本,但它非常适合颜色从A到B的动画。"ofInt()"对我来说效果很差。 - Chanh

13

在ColorUtils中使用blendARGB的最佳方法。

简单且代码行数最少。

view.setBackgroundColor(ColorUtils.blendARGB(Color.parseColor("#FFFFFF"), Color.parseColor("#CCCCCC"), fraction));

非常精妙和美妙的解决方案!谢谢! - Georgiy Chebotarev

11

在过渡非常不同的颜色时,其他答案给我带来了奇怪的效果。从黄色到灰色,在动画过程中会在某个时刻变成绿色。

对我而言最有效的方法是下面的代码片段。这将创建一个非常平滑的过渡,没有出现奇怪的颜色。

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);
}

你的 blendColors 方法似乎是 ArgbEvaluator 的重新实现。 - Grzegorz Adam Hankiewicz
这个很好地运作了。我尝试使用ArgbEvaluator,但由于某些原因它返回的是分数而不是arg值。 - vovahost

8
您可以使用TransitionDrawable。
TransitionDrawable transition = (TransitionDrawable) viewObj.getBackground();
transition.startTransition(transitionTime);

在drawable文件夹中创建xml文件,你可以写入以下内容:

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

然后,在实际的视图xml中,您将在android:background属性中引用此TransitionDrawable。


你能否在这里提供一个完整的解决方案,说明如何在android:background属性中使用它? - real 19
1
android:background="@drawable/color_transition" 安卓:背景="@drawable/color_transition" - Muhammad Aamir Ali
@real19 或者通过编程方式使用 view.setBackground(getResources().getDrawable(drawableId)); - David Artmann

5
下面的代码片段完美地运行了。我曾考虑使用ValueAnimator.ofArgb(),但那需要至少API 21。相反,下面的代码适用于API 11及更高版本。它实现了平滑地改变颜色。
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();

4
如果您像我一样想要将RGB颜色转换为白色或黑色,您会发现使用@bcorso的答案很困难,因为这是错误的颜色空间。并不是说答案是错误的,我不是这个意思。
以下是使用颜色colorA进行混合的示例,使用白色(可以用黑色替代):
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));
});

有很多人对这两个概念感到困惑,因此这里提供一张图片来进行直观的描述。 HSL和HSV


4
我发现Android源代码中ArgbEvaluator的实现在颜色过渡方面表现最佳。使用HSV时,根据两种颜色的不同,过渡可能会跳过太多色相。但这种方法不会。如果您想简单地进行动画处理,请像这里建议的那样使用ArgbEvaluatorValueAnimator
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(); 

然而,如果你像我一样想将转换与某些用户手势或从输入传递的其他值相结合,那么 ValueAnimator 并没有什么帮助(除非你的目标是 API 22 及以上版本,此时可以使用 ValueAnimator.setCurrentFraction() 方法)。当针对低于 API 22 的版本时,将在 ArgbEvaluator 源代码 中找到的代码包装在自己的方法中,如下所示:
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))));
}

并且您可以随意使用它。


3
解决方法:通过递归地改变色调来解决。
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);
    }

2
你应该使用ValueAnimator来完成这种类型的事情。我已经发布了一个关于如何使用HSV值完成此操作的答案。 - bcorso

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接