In general, I'm trying to animate on an array of int values, unlike the typical case where you are just animating an int or float from one number to another.
To be specific, I'm trying to animate the int [] of colors on a GradientDrawable object.
GradientDrawable has a property named "setColors(int []) " in which I set 2 colors, the starting color and ending color, which make up a whole gradient.
I want to animate from one combination of colors towards another. If it were a solid color, I could already do this, like the following:
Integer colorFrom = getResources().getColor(R.color.red);
Integer colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setBackgroundColor((Integer)animator.getAnimatedValue());
}
});
colorAnimation.start();
So, I need something like this:
//Define 2 starting colors
Integer colorFrom1 = getResources().getColor(R.color.red);
Integer colorFrom2 = getResources().getColor(R.color.darkred);
int[] startColors = new int[] {colorFrom1, colorFrom2};
//Define 2 ending colors
Integer colorTo1 = getResources().getColor(R.color.blue);
Integer colorTo2 = getResources().getColor(R.color.darkblue);
int[] endColors = new int[] {colorTo1, colorTo2};
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), startColors, endColors);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
gradientView.setColors((Integer)animator.getAnimatedValues()[0] , (Integer)animator.getAnimatedValues()[1]);
}
});
colorAnimation.start();
Obvioulsy that code won't exist because there is no getAnimatedValues() method returning an array and furthermore there is no ValueAnimator.ofObject method which accepts an array as a start and end values.
Any ideas?
My only idea now is to run two animators in parallel, each animating one dimension of the gradient, and each setting only half of the array accepted by gradientDrawable.setColors().... but boyyyy that would be nearly unacceptably inefficient and possibly dangerously out-of-sync.
How about this?
public class ArgbArrayEvaluator implements TypeEvaluator<Integer[]> {
ArgbEvaluator evaluator = new ArgbEvaluator();
public Integer[] evaluate(float fraction, Integer[] startValues, Integer[] endValues) {
if(startValues.length != endValues.length) throw new ArrayIndexOutOfBoundsException();
Integer[] values = new Integer[startValues.length];
for(int = 0;i<startValues.length;i++) {
values[i] = (Integer) evaluator.evaluate(fraction,startValues[i],endValues[i]);
}
return values;
}
}
Then do
/* Make sure startColors and endColors are Integer[] not int[] */
final ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbArrayEvaluator(),startColors,endColors);
Your listener code:
public void onAnimationUpdate(ValueAnimator animator) {
Integer[] values = (Integer[]) animator.getAnimatedValue();
gradientView.setColors(values[0],values[1]);
}
Alternatively, with ObjectAnimator
final ObjectAnimator colorAnimation = ObjectAnimator.ofMultiInt(gradientView,"colors",null, new ArgbArrayEvaluator(), startColors,endColors);
Also, in the ValueAnimator documentation for the start() method it says:
The animation started by calling this method will be run on the thread that called this method. This thread should have a Looper on it (a runtime exception will be thrown if this is not the case). Also, if the animation will animate properties of objects in the view hierarchy, then the calling thread should be the UI thread for that view hierarchy.
So I would use the following if you're not already in the UI thread.
runOnUiThread(new Runnable() {
public void run() {
colorAnimation.start();
}
});
I think it'll work!
Background
It's possible to change the background of the actionbar, and even animate between two colors, as such:
public static void animateBetweenColors(final ActionBar actionBar, final int colorFrom, final int colorTo,
final int durationInMs) {
final ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
ColorDrawable colorDrawable = new ColorDrawable(colorFrom);
#Override
public void onAnimationUpdate(final ValueAnimator animator) {
colorDrawable.setColor((Integer) animator.getAnimatedValue());
actionBar.setBackgroundDrawable(colorDrawable);
}
});
if (durationInMs >= 0)
colorAnimation.setDuration(durationInMs);
colorAnimation.start();
}
The problem
I can't find a way to get the view of the action mode, so that I could change its background on some cases (while it's showing).
What I tried
Only thing I found is a hack-y way which assumes that the id of the action mode will stay the same, and even this would work just for the view of the "done" button (the one that looks like an "V" and is actually more like "cancel").
I also found how to change it via themes, but that's not what I need, since I need to do it programmatically.
The question
How do I get the view of the actionMode, or, more precisely, how can I change its background using an animation?
How do I get the view of the actionMode, or, more precisely, how can I
change its background using an animation?
You have two choices, unfortunately neither of which involve native ActionMode APIs:
The ActionBarContextView is responsible for controlling the ActionMode
Use Resources.getIdentifier to call Activity.findViewById and pass in the ID the system uses for the ActionBarContextView
Use reflection to access to Field in ActionBarImpl
Here's an example of both:
Using Resources.getIdentifier:
private void animateActionModeViaFindViewById(int colorFrom, int colorTo, int duration) {
final int amId = getResources().getIdentifier("action_context_bar", "id", "android");
animateActionMode(findViewById(amId), colorFrom, colorTo, duration);
}
Using reflection:
private void animateActionModeViaReflection(int colorFrom, int colorTo, int duration) {
final ActionBar actionBar = getActionBar();
try {
final Field contextView = actionBar.getClass().getDeclaredField("mContextView");
animateActionMode((View) contextView.get(actionBar), colorFrom, colorTo, duration);
} catch (final Exception ignored) {
// Nothing to do
}
}
private void animateActionMode(final View actionMode, final int from, int to, int duration) {
final ValueAnimator va = ValueAnimator.ofObject(new ArgbEvaluator(), from, to);
final ColorDrawable actionModeBackground = new ColorDrawable(from);
va.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animator) {
actionModeBackground.setColor((Integer) animator.getAnimatedValue());
actionMode.setBackground(actionModeBackground);
}
});
va.setDuration(duration);
va.start();
}
Results
Here's a gif of the results animating from Color.BLACK to Color.BLUE at a duration of 2500:
How do you animate the change of background color of a view on Android?
For example:
I have a view with a red background color. The background color of the view changes to blue. How can I do a smooth transition between colors?
If this can't be done with views, an alternative will be welcome.
You can use new Property Animation Api for color animation:
int colorFrom = getResources().getColor(R.color.red);
int colorTo = getResources().getColor(R.color.blue);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.setDuration(250); // milliseconds
colorAnimation.addUpdateListener(new AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animator) {
textView.setBackgroundColor((int) animator.getAnimatedValue());
}
});
colorAnimation.start();
For backward compatibility with Android 2.x use Nine Old Androids library from Jake Wharton.
The getColor method was deprecated in Android M, so you have two choices:
If you use the support library, you need to replace the getColor calls with:
ContextCompat.getColor(this, R.color.red);
if you don't use the support library, you need to replace the getColor calls with:
getColor(R.color.red);
I ended up figuring out a (pretty good) solution for this problem!
You can use a TransitionDrawable to accomplish this. For example, in an XML file in the drawable folder you could write something like:
<?xml version="1.0" encoding="UTF-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The drawables used here can be solid colors, gradients, shapes, images, etc. -->
<item android:drawable="#drawable/original_state" />
<item android:drawable="#drawable/new_state" />
</transition>
Then, in your XML for the actual View you would reference this TransitionDrawable in the android:background attribute.
At this point you can initiate the transition in your code on-command by doing:
TransitionDrawable transition = (TransitionDrawable) viewObj.getBackground();
transition.startTransition(transitionTime);
Or run the transition in reverse by calling:
transition.reverseTransition(transitionTime);
See Roman's answer for another solution using the Property Animation API, which wasn't available at the time this answer was originally posted.
Depending on how your view gets its background color and how you get your target color there are several different ways to do this.
The first two uses the Android Property Animation framework.
Use a Object Animator if:
Your view have its background color defined as a argb value in a xml file.
Your view have previously had its color set by view.setBackgroundColor()
Your view have its background color defined in a drawable that DOES NOT defines any extra properties like stroke or corner radiuses.
Your view have its background color defined in a drawable and you want to remove any extra properties like stroke or corner radiuses, keep in mind that the removal of the extra properties will not animated.
The object animator works by calling view.setBackgroundColor which replaces the defined drawable unless is it an instance of a ColorDrawable, which it rarely is. This means that any extra background properties from a drawable like stroke or corners will be removed.
Use a Value Animator if:
Your view have its background color defined in a drawable that also sets properties like the stroke or corner radiuses AND you want to change it to a new color that is decided while running.
Use a Transition drawable if:
Your view should switch between two drawable that have been defined before deployment.
I have had some performance issues with Transition drawables that runs while I am opening a DrawerLayout that I haven't been able to solve, so if you encounter any unexpected stuttering you might have run into the same bug as I have.
You will have to modify the Value Animator example if you want to use a StateLists drawable or a LayerLists drawable, otherwise it will crash on the final GradientDrawable background = (GradientDrawable) view.getBackground(); line.
Object Animator:
View definition:
<View
android:background="#FFFF0000"
android:layout_width="50dp"
android:layout_height="50dp"/>
Create and use a ObjectAnimator like this.
final ObjectAnimator backgroundColorAnimator = ObjectAnimator.ofObject(view,
"backgroundColor",
new ArgbEvaluator(),
0xFFFFFFFF,
0xff78c5f9);
backgroundColorAnimator.setDuration(300);
backgroundColorAnimator.start();
You can also load the animation definition from a xml using a AnimatorInflater like XMight does in Android objectAnimator animate backgroundColor of Layout
Value Animator:
View definition:
<View
android:background="#drawable/example"
android:layout_width="50dp"
android:layout_height="50dp"/>
Drawable definition:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/>
<stroke
android:color="#edf0f6"
android:width="1dp"/>
<corners android:radius="3dp"/>
</shape>
Create and use a ValueAnimator like this:
final ValueAnimator valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(),
0xFFFFFFFF,
0xff78c5f9);
final GradientDrawable background = (GradientDrawable) view.getBackground();
currentAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(final ValueAnimator animator) {
background.setColor((Integer) animator.getAnimatedValue());
}
});
currentAnimation.setDuration(300);
currentAnimation.start();
Transition drawable:
View definition:
<View
android:background="#drawable/example"
android:layout_width="50dp"
android:layout_height="50dp"/>
Drawable definition:
<?xml version="1.0" encoding="utf-8"?>
<transition xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<solid android:color="#FFFFFF"/>
<stroke
android:color="#edf0f6"
android:width="1dp"/>
<corners android:radius="3dp"/>
</shape>
</item>
<item>
<shape>
<solid android:color="#78c5f9"/>
<stroke
android:color="#68aff4"
android:width="1dp"/>
<corners android:radius="3dp"/>
</shape>
</item>
</transition>
Use the TransitionDrawable like this:
final TransitionDrawable background = (TransitionDrawable) view.getBackground();
background.startTransition(300);
You can reverse the animations by calling .reverse() on the animation instance.
There are some other ways to do animations but these three is probably the most common. I generally use a ValueAnimator.
You can make an object animator. For example, I have a targetView and I want to change your background color:
int colorFrom = Color.RED;
int colorTo = Color.GREEN;
int duration = 1000;
ObjectAnimator.ofObject(targetView, "backgroundColor", new ArgbEvaluator(), colorFrom, colorTo)
.setDuration(duration)
.start();
If you want color animation like this,
this code will help you:
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
anim.setDuration(2000);
float[] hsv;
int runColor;
int hue = 0;
hsv = new float[3]; // Transition color
hsv[1] = 1;
hsv[2] = 1;
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
hsv[0] = 360 * animation.getAnimatedFraction();
runColor = Color.HSVToColor(hsv);
yourView.setBackgroundColor(runColor);
}
});
anim.setRepeatCount(Animation.INFINITE);
anim.start();
best way is to use ValueAnimator and ColorUtils.blendARGB
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setDuration(325);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fractionAnim = (float) valueAnimator.getAnimatedValue();
view.setBackgroundColor(ColorUtils.blendARGB(Color.parseColor("#FFFFFF")
, Color.parseColor("#000000")
, fractionAnim));
}
});
valueAnimator.start();
The documentation on XML powered animations is horrible. I've searched around hours just to animate the background color of a button when pressing... The sad thing is that the animation is only one attribute away: You can use exitFadeDuration in the selector:
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="200">
<item android:state_pressed="true">
<shape android:tint="#3F51B5" />
</item>
<item>
<shape android:tint="#F44336" />
</item>
</selector>
Then just use it as background for your view. No Java/Kotlin code needed.
Another easy way to achieve this is to perform a fade using AlphaAnimation.
Make your view a ViewGroup
Add a child view to it at index 0, with match_parent layout dimensions
Give your child the same background as the container
Change to background of the container to the target color
Fade out the child using AlphaAnimation.
Remove the child when the animation is complete (using an AnimationListener)
Here's a nice function that allows this:
public static void animateBetweenColors(final #NonNull View viewToAnimateItsBackground, final int colorFrom,
final int colorTo, final int durationInMs) {
final ColorDrawable colorDrawable = new ColorDrawable(durationInMs > 0 ? colorFrom : colorTo);
ViewCompat.setBackground(viewToAnimateItsBackground, colorDrawable);
if (durationInMs > 0) {
final ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(animator -> {
colorDrawable.setColor((Integer) animator.getAnimatedValue());
ViewCompat.setBackground(viewToAnimateItsBackground, colorDrawable);
});
colorAnimation.setDuration(durationInMs);
colorAnimation.start();
}
}
And in Kotlin:
#JvmStatic
fun animateBetweenColors(viewToAnimateItsBackground: View, colorFrom: Int, colorTo: Int, durationInMs: Int) {
val colorDrawable = ColorDrawable(if (durationInMs > 0) colorFrom else colorTo)
ViewCompat.setBackground(viewToAnimateItsBackground, colorDrawable)
if (durationInMs > 0) {
val colorAnimation = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.addUpdateListener { animator: ValueAnimator ->
colorDrawable.color = (animator.animatedValue as Int)
ViewCompat.setBackground(viewToAnimateItsBackground, colorDrawable)
}
colorAnimation.duration = durationInMs.toLong()
colorAnimation.start()
}
}
This is the method I use in a Base Activity to change background. I'm using GradientDrawables generated in code, but could be adapted to suit.
protected void setPageBackground(View root, int type){
if (root!=null) {
Drawable currentBG = root.getBackground();
//add your own logic here to determine the newBG
Drawable newBG = Utils.createGradientDrawable(type);
if (currentBG==null) {
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN){
root.setBackgroundDrawable(newBG);
}else{
root.setBackground(newBG);
}
}else{
TransitionDrawable transitionDrawable = new TransitionDrawable(new Drawable[]{currentBG, newBG});
transitionDrawable.setCrossFadeEnabled(true);
if(Build.VERSION.SDK_INT<Build.VERSION_CODES.JELLY_BEAN){
root.setBackgroundDrawable(transitionDrawable);
}else{
root.setBackground(transitionDrawable);
}
transitionDrawable.startTransition(400);
}
}
}
update: In case anyone runs in to same issue I found, for some reason on Android <4.3 using setCrossFadeEnabled(true) cause a undesirable white out effect so I had to switch to a solid colour for <4.3 using #Roman Minenok ValueAnimator method noted above.
Answer is given in many ways. You can also use ofArgb(startColor,endColor) of ValueAnimator.
for API > 21:
int cyanColorBg = ContextCompat.getColor(this,R.color.cyan_bg);
int purpleColorBg = ContextCompat.getColor(this,R.color.purple_bg);
ValueAnimator valueAnimator = ValueAnimator.ofArgb(cyanColorBg,purpleColorBg);
valueAnimator.setDuration(500);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
relativeLayout.setBackgroundColor((Integer)valueAnimator.getAnimatedValue());
}
});
valueAnimator.start();
Use the below function for Kotlin:
private fun animateColorValue(view: View) {
val colorAnimation =
ValueAnimator.ofObject(ArgbEvaluator(), Color.GRAY, Color.CYAN)
colorAnimation.duration = 500L
colorAnimation.addUpdateListener { animator -> view.setBackgroundColor(animator.animatedValue as Int) }
colorAnimation.start()
}
Pass whatever view you want to change color of.
Roman Minenok answer in kotlin and as an extension function
fun View.colorTransition(#ColorRes startColor: Int, #ColorRes endColor: Int, duration: Long = 250L){
val colorFrom = ContextCompat.getColor(context, startColor)
val colorTo = ContextCompat.getColor(context, endColor)
val colorAnimation: ValueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.duration = duration
colorAnimation.addUpdateListener {
if (it.animatedValue is Int) {
val color=it.animatedValue as Int
setBackgroundColor(color)
}
}
colorAnimation.start()
}
If you want to change from current background color to new color then you can use this
fun View.colorTransition(#ColorRes endColor: Int, duration: Long = 250L){
var colorFrom = Color.TRANSPARENT
if (background is ColorDrawable)
colorFrom = (background as ColorDrawable).color
val colorTo = ContextCompat.getcolor(context, endColor)
val colorAnimation: ValueAnimator = ValueAnimator.ofObject(ArgbEvaluator(), colorFrom, colorTo)
colorAnimation.duration = duration
colorAnimation.addUpdateListener {
if (it.animatedValue is Int) {
val color=it.animatedValue as Int
setBackgroundColor(color)
}
}
colorAnimation.start()
}
Usage
myView.colorTransition(R.color.bg_color)
You can use ValueAnimator like this:
fun startColorAnimation(v: View) {
val colorStart = v.solidColor
val colorEnd = Color.RED
val colorAnim: ValueAnimator = ObjectAnimator.ofInt(v, "backgroundColor", colorStart, colorEnd)
colorAnim.setDuration(1000)
colorAnim.setEvaluator(ArgbEvaluator())
colorAnim.repeatCount = 1
colorAnim.repeatMode = ValueAnimator.REVERSE
colorAnim.start()
}
add a folder animator into res folder. (the name must be animator). Add an animator resource file. For example res/animator/fade.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator
android:propertyName="backgroundColor"
android:duration="1000"
android:valueFrom="#000000"
android:valueTo="#FFFFFF"
android:startOffset="0"
android:repeatCount="-1"
android:repeatMode="reverse" />
</set>
Inside Activity java file, call this
View v = getWindow().getDecorView().findViewById(android.R.id.content);
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.fade);
set.setTarget(v);
set.start();
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 method's 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.
Based on ademar111190's answer, I have created this method the will pulse the background color of a view between any two colors:
private void animateBackground(View view, int colorFrom, int colorTo, int duration) {
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(view, "backgroundColor", new ArgbEvaluator(), colorFrom, colorTo);
objectAnimator.setDuration(duration);
//objectAnimator.setRepeatCount(Animation.INFINITE);
objectAnimator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
// Call this method again, but with the two colors switched around.
animateBackground(view, colorTo, colorFrom, duration);
}
#Override
public void onAnimationCancel(Animator animation) {
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
objectAnimator.start();
}
You can use ArgbEvaluatorCompat class above API 11.
implementation 'com.google.android.material:material:1.0.0'
ValueAnimator colorAnim = ValueAnimator.ofObject(new ArgbEvaluatorCompat(), startColor, endColor);
colorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
mTargetColor = (int) animation.getAnimatedValue();
}
});
colorAnim.start();