Wednesday, August 24, 2011

The Mysterious Behavior of fillBefore, fillAfter, and fillEnabled

A bug was filed on android.com recently that had me poring through the code and docs to understand three boolean flags in the old Animation class: fillBefore, fillAfter, and fillEnabled. On the surface, these properties don't seem that difficult to understand; they control how animations behave before and after they run. But between some complicated interactions of the properties and some, er, inaccuracies in the reference docs, it turned out to be somewhat tricky to understand how they work and how they're supposed to work. Once I got through that exercise (including fixing the docs - look for those fixes in a future release), I thought it might be helpful to explain how these flags really work.

First, let's cover the behavior of fillBefore and fillAfter. We'll get to fillEnabled and it's, um, special behavior after that.

Before and After

fillBefore and fillAfter are pretty simple, conceptually; they define how an animation behaves before and after it runs. fillBefore controls whether the initial value of the animation is applied before its start time and fillAfter controls whether the animation's ending value persists after it ends. There are a couple of important nuances to understand, however.

start time: The starting time of an animation, for the purposes of fillBefore, is not the time when you call startAnimation() on a View. Rather, it's the time that the animation will actually start running. These two times are the same if there is no startOffset set on the animation, but if you want to delay your animation, you might set a startOffset value to achieve that delay. fillBefore controls whether the animation will use the initial value of the animation during that startOffset phase.

AnimationSet: If you want to control the fill behavior of an animation that is inside an AnimationSet, it is certainly possible to do this. But if you want to control what happens outside the runtime of that AnimationSet, then you need to set the fill behavior on the set itself. For example, the fillAfter flag controls whether a ScaleAnimation's end value is used after it ends. But if the animation you set on your View is actually an AnimationSet containing that inner ScaleAnimation, then you need to set fillAfter on the AnimationSet if you want the value to persist after the AnimationSet ends. You can think of the fill behavior flags as having scope, which is either global (when not contained in an AnimationSet) or local to the AnimationSet in which their contained. Or you can just play with them enough to get the hang of it, like I did. It's also worth noting, as stated in the docs (correctly this time) that if you set the value of fillBefore or fillAfter these values will override those in the child animations of the set.

The default values for these flags are true for fillBefore and false for fillAfter. So by default, animations will set their initial value immediately when the animation starts (regardless of startOffset), but will not persist those values after they end.

So that's all there is to those flags: you set or unset them according to whether you want the animation values to be used outside of when the animation is actually running. Well, sort of...

The Enabler

Here's where the other flag, fillEnabled, comes in. This flag controls when the other flags are actually taken into account. Or that's what some of the docs would have you believe. In actual fact, this flag controls only the behavior of fillBefore, and essentially leaves fillAfter to its own devices.

Here's how the value of fillEnabled works:

false: If fillEnabledis false (which it is by default), then the value of fillBefore will be ignored. That's right, you can set or unset it all you want, but it will ignore your wishes and will essentially assume that fillBefore is true.

true: When fillEnabled is true, the value of fillBefore will be taken into account to determine whether to apply the animation before it begins. The value of fillAfter, as I said earlier, will be used as-is, regardless of the value of fillEnabled.

All of this means that the only way to get an animation to not persist its starting value before it actually starts running is to set bothfillEnabled to true and fillBefore to false. Any other combination will result in the animation being applied before its starting time. Meanwhile, the value of fillAfter is applied directly, regardless of the value of fillEnabled. I believe it is this asymmetric behavior (coupled with an unfortunately generically named "fillEnabled" property and some, well, bugs in the docs) that made these three variables particularly difficult for some people to understand. Some people like me.

At this point, you might be asking yourself why these variables were created and defined in this way. All I can say is, welcome to the wonderful world of API development, where behavior needs to evolve while compatibility is preserved. In any case, I hope this explanation helps those who needed it.

There's no compatibility library for property animations, at least not yet. Given time (which always seems to be in short supply), I could make one. But the utility of that animation system for Android loses a lot of benefits once you take away the new properties (alpha, translationX/Y, etc.) added to View. We can't add new API to older releases, so that severely limits what you could do with property animators for Android UIs...

I have tried for many hours now to make a fairly simple animation work and believe I have almost ran all possible ways. Unfortunately your post didn't help me either.

Here is the deal: I want a button to shrink after being pressed. After the scaling down (onAnimationEnd()) it has to start a new animation that spins round the button. But every time the spinning starts it comes back to normal size..

The animations are in two different xml files and are at the root (so no ). The rotate.xml has fillEnabled=true and fillBefore=false. the scaleStart.xml has android:fillEnabled="true" android:fillAfter="true" android:fillBefore="true".

I think you're running into a problem where your animations are independent of each other and are therefore clobbering each others values. An AnimationSet automatically concatenates the matrices of its child animations together to get a blended result (translated and rotated, or whatever). In your case, you scale the button down, tell that animation to hold its value (which works just fine). But... then you start another animation which sets the button's transform independently.

I suggest you put these two animations in an AnimationSet and try again. This kind of situation is exactly what AnimationSet is for.

有中文版的解释吗？这么一大偏英文的说明，实在是难以理解。在中国大陆，能解释清楚这三个之间的关系的人很少。希望能得到你的答复。is there a explanation by Chinese? It's too hard to understand.In China,nobody can explanation the relation bettwen them.I had search from google and baidu. hope you can reply.thanks

Hi Chet,i have use .layout() function to make my layout move to the new position and its work great but whenever the window focus changes my layout came back to its orignal position??can you please help...Also i have read Romain Guy's comment in SO,that you sould never use the .layout() function,as it should only be used by the framework,but if that is so then why framework is offering this function to us??Quite strange!!

@Raja: layout() is only intended for use by custom layout classes to set the size of their children as part of the measurement and layout process, as described in the docs for that method. It is absolutely not recommended that it is called directly in any other situation for exactly the reason you're hitting. It will set the size of child views, but if the container of those views runs layout at some later time, it will resize those views to the correct size as determined by layout.The only proper way to set the size of views is to set the layout parameters and call requestLayout() to run a proper layout pass.

@Chet Thanx now yes i have realized that later,whenever some view in the layout is invalidated the frameworks call the layout function and it gets repositioned (dont respecting the layout call :P),the only way to keep changes presistence is to set Layout Params,,any way thanx for the help

Here I wish to apply second rotate after 200 ms time (on end result of 1st rotate). But in result, I see that android:fromDegrees (2nd rotate) is applied even before 200 ms as a default 20 degree rotation for first 200ms. I expect the entire block to take effect only after 200 ms. How do I overcome this ?

Also is there any thumb rule to understand android:fromDegrees,android:toDegreesSince it can take negative values as well as values like 0-20 or 20-0.How do we interpret these values correctly ?

I am using Translate animation to translate a view from say Pos1 to Pos 2. I had set the property fillAfter to true. This works pretty well. But as soon as I say view.clearAnimation, the View resets back to its position Pos1. Why does this happen ?

@sagar: because you cleared the animation. The 'fill' property only matters if an animation is actually present on a view. Once you clear the animation, you've removed the thing that was being used to 'fill'. Note that TranslationAnimation (along with all of the other pre-3.0 animation objects) don't actually change properties on views (there is no 'translation' property pre-3.0). So the only way they get drawn differently is by the animation changing how the views are drawn. Remove the animation and you remove that drawing change.

My Comedy Books

My Programming Books

About Me

I'm a software geek, working at Google, making Android graphics and animation more excellent. In previous lives I've worked at Sun on the JDK, at Adobe on Flex, and various other places in Silicon Valley, always working on graphics software.

In my copious spare time, I write. I write humor on my blog Enough About You... along with my G+ stream at google.com/+ChetHaase and on Twitter via @chethaase. I also occasionally post technical articles on CodeDependent. I co-wrote the book Filthy Rich Clients with Romain Guy, wrote another programming book Flex 4 Fun about Flex graphics and animation, and wrote humor books Round and Holy, When I am King.... and the long-anticipated sequel, When I am King... II. Like women and childbirth, I eventually forget the pain of the process of writing a book, and will probably make the mistake of writing another one eventually. As soon as the scars from the last one heal.

I also have developed a strange and disturbing attraction to the microphone. Any microphone. You may find me giving a technical talk at a developer conference or user group, or doing some standup or improv in a comedy show. I've also been seen in videos ("You may know me from such hits as DevBytes..."), either work-related or posted on my comedy blog and YouTube channel.

None of what I write in my blogs, on Google+, or anywhere else has anything to do with my employer; they're just my thoughts, my jokes, my mistakes.