Question

I'm doing a fade-in/fade-out animation between two activities in Android. Both activities have a white background (Holo light theme). I change alpha from 0 to 1 in the activity that fades in, and from 1 to 0 in the activity that fades out.

My problem is that the perceived background color is not constant across the animation, which I find annoying. That is, the background darkens and then lightens over the duration of the animation. So in the middle of the animation the background will not be white, but rather a middle gray.

I'd like to use two interpolators such that the combined effect of both gives a constant background color. I thought that linear interpolators would do the trick, since they keep the sum of the two alphas constant. But it doesn't work: the background color changes during the animation. The standard accelerate and decelerate don't have the behaviour I want, either.

So, my question is: are there any known interpolators (perhaps accelerate, decelerate with certain rates) that give a constant background color in a fade-in / fade-out animation?

Was it helpful?

Solution 2

I got it!

Short answer: eliminate one of the two animations. When going from main to secondary activity, use only a fade-in. When going from secondary to main activity, use only a fade-out. Details and explanation follow.

There are several ways to combine two colors with alpha values. The process is called "alpha compositing". One naive way of doing the combination, which is not the one commonly used, is:

(a) C = C1*alpha1 + C2*alpha2

If that combination method were applied, using two linear interpolators for alpha1 and alpha2 would give a constant color wherever both C1 and C2 are equal to a "background" color Cbg. Specifically, if alpha1 = t (where t represents time), alpha2 = 1-t and C1 = C2 = Cbg, the combination (a) gives C = Cbg*t + Cbg*(1-t) = Cbg. That is, the composite color C equals Cbg irrespective of t, as desired.

However, it appears that the combination which Android is applying is the so-called "over" operator:

(b) C = C1*alpha1 + C2*alpha2*(1-alpha1)

where it is considered that the pixel with color C1 is over that with C2. With the combination method (b), in order to get a constant color C for pixels with C1 = C2 = Cbg it is sufficient that alpha2 = 1, because then C = Cbg*alpha1 + Cbg*1*(1-alpha1) = Cbg.

So the interpolator should be any (linear or otherwise) for the activity which is over and 1 (no animation) for the activity that is under.

Since I wasn't sure how Android decides which activity is over and which is under, I did some experimentation and found that:

  • When the main activity creates the secondary activity, the adequate procedure is to use a fade-in interpolator for the secondary activity.

  • When the secondary activity goes back to the main activity, the adequate procedure is to set a fade-out interpolator for the secondary activity.

So it looks like Android is considering that the secondary activity is over the main one. This makes sense, since the secondary was created by the main one.

  • Since the main activity doesn't have to use any animation, according to the documentation it should be sufficient to set its animation to 0, that is, use overridePendingTransition(R.anim.fadein, 0). However, I have found that this only works sometimes. To get it working, I need to define a "fake" fade which goes from alpha 1.0 to alpha 1.0 (so that it actually does nothing) and use overridePendingTransition(R.anim.fadein, R.anim.fakefade).

As for the need of a fake animation instead of a 0 animation, I don't have an explanation.

I wish the Android documentation was clearer on this. It would have saved me quite some time.

OTHER TIPS

From the comments, it sounds like you want to animate the views in the activities, rather than the Activities themselves. You can do this using the droidQuery library. Simply set this as your onPause() method:

@Override
public void onPause() {
    //here, substitute mainView with the id of your background
    $.with(this, R.id.mainView).selectChildren().selectAll().animate("{ alpha : 0.0f }", 400, $.Easing.LINEAR, $.noop());
    super.onPause();
}

This will select all of the views that are children of your background, and animate their alpha values to 0 using ObjectAnimator, Linear interpolation, and 400ms duration.

You will also want to override onResume():

@Override
public void onResume() {
    //here, substitute mainView with the id of your background
    $.with(this, R.id.mainView).selectChildren().selectAll().animate("{ alpha : 1.0f }", 400, $.Easing.LINEAR, $.noop());
    super.onResume();
}

This does the opposite - animating from 0 alpha to 1.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top