Wednesday, January 4, 2012

Curved Motion in Android

The animation support added in Android 3.0 and enhanced since then is useful, allowing a flexible system of property animation to animate literally anything you want. Like a store going out of business: if it ain't nailed down, it's up for grabs.

But that doesn't mean we're done yet; there are many things that we'd like to do to keep improving the animation system and making Android animations easier to write and more powerful. One of those things is curved motion.

Right now, if you animate something between a start and end point, it will move in a straight line between those values. For example, a translation animation, where the object moves from one (x, y) location to another, will move in a straight line beween those points. You can (and should) use non-linear timing for these animations, and the animation framework uses ease-in/ease-out timing by default. But the actual spacial movement is linear; there is no way for you to specify a curved path for an object to take between the end values of an animation.

Even if you use a multi-point keyframe animation, where you specify several intermediate points for the animation to pass through along the way, you are still going to move linearly between each of those points.

One goal with animations is to make them feel as natural as possible. And just as motion in the real world (you know, the one we have to use as we move between the machines of our lives) is not linear, animations in our GUIs should not be limited to linear. For example, f a view moves from corner of the screen to the opposite corner, wouldn't it be nice to have the option to give it a little curve in and out at both ends, instead of being locked into a straight-line movement?

We'd like to add this kind of capability to the animation system in a future release; it needs to be easy to create such animations, we just have to provide the APIs and functionality to let you do it.

As I was looking into the problem, I created some prototype code using the existing animation APIs and realized that there's nothing stopping you from having this capability already. The flexibility of the Animator APIs allow you to do exactly the kinds of operations you need to get curved motion. You just need a little more code to do it.

I thought it would help to post some of my prototype code to show you how. In particular, I thought this sample was interesting because it shows:

How to move things along curves and complex paths

How to use the Animator APIs to do more than what the base library supports. In particular, how to use TypeEvaluator to animate custom types.

Some notes and caveats before we begin:

This is a prototype only, and does not necessarily represent the way it would appear in any final API. It's just a sample program, and a pretty simple one at that.

Simply computing the location based on the fraction time elapsed in a curve interval is probably not the motion you want. It will give the mathematically correct motion along that path, but the time spent traveling along any particular length of that curve is dependent on the structure of the curve. Basically, you'll end up with slower and faster portions of the curve. This problem is admirably described on this blog. A more complete solution flattens the curve and ensures uniform speed. But again, it's just a simple demo, so I'll leave correct path-time-distance navigation as an exercise for the reader (and for the writer, since this would be a part of any future implementation in the SDK).

The timing of the animation along a multiple-point path such as the one in the demo app is not as flexible as I'd like it to be. Basically, you end up with something that gives equal time in the overall animation to each individual interval. In addition, any "move" operations in the middle of the path cause the animation to wait at that location for that interval's equal slice of the duration. It should be possible, in a more complete implementation, to define the time associated with any particular interval.

This description assumes a foreknowledge of Bézier curves; if you have no idea what I'm talking about, you might want to go look them up (such as on the blog linked above or various other places on the web, such as Wikipedia). Or you can just read along, refer to the mathematically imprecise sketch to the right, and hopefully not get too lost.

The code as written requires Android 4.0 to build. Actually, it's mostly compatible back to 3.0, but the PathEvaluator class uses a generic specification for TypeEvaluator that was introduced in 4.0 (not necessary, just convenient when I wrote the code).

On with the code.

The activity code is in PathAnimationActivity.onCreate(). First, we set up the path itself:

Here, we are constructing an AnimatorPath (which is part of the demo project that we'll see below) and supplying it with operations that will become points in the path, along with the operations to navigate the intervals up to those points. The first operation defines where the path starts, (0, 0). Then we move in a straight line to (0, 300). Finally, we move along a curve (a cubic Bézier curve, to be precise) to the point (400, 500), using control points (100, 0) and (300, 900) along the way.

This animator uses a new PathEvaluator object (introduced below). It also queries the AnimatorPath object to get an array of PathPoint (covered below) objects; these will become the points in the animation that define the intervals that we animate between. The animator will send the animated values to the this object, which is the activity instance itself. We implement the setter below to receive those values and pass them along to the actual Button object that we want to move on the screen:

Internally, AnimatorPath uses PathPoint to store the information at each point along the path. The PathPoint class is a simple data structure that just holds an x/y location, optional control point information (for curves), and the operation that tells the path containing that path point how to nagivate the interval leading up to that point. There are three factory methods that AnimatorPath uses to construct PathPoints as its API is called:

All of the logic of actually animating between points along the path (besides that in the Android Animator engine itself) is in the class PathEvaluator. This class implements the single method in the TypeEvaluator interface, evaluate():

Finally, we create a new PathPoint with this calculated xy information, which will be passed to the setButtonLoc() method seen above:

return PathPoint.moveTo(x, y);

... and that's it. There's really not much to it, which was the whole point of posting this code. If you want to add curved motion to your animations, you can wait for the framework to do it for you... or you can take what I've done here and modify it to suit your needs. Yay, TypeEvaluator!

@hazam: Reusing the existing Path API is definitely preferable, and is something I'd try to do in any version of this feature that got integrated into the SDK. The API was a bit complex for the simple things I was trying to do in my demo/prototype, so I skipped it in this particular case. But I wouldn't want to introduce a new kind of 'path' object just for the case of animation when there's a full-featured path in the library already.

@Jens: I can't say what the future holds, but a graphical/animation tool would be quite different from the tools we offer so far, and would be a huge undertaking. It wouldn't be hard to write a simple one-off tool that just allowed you to draw a path and animate an object along it, but generalizing it to the larger case of animations overall wouldn't be trivial.

@Ian: Bezier splines seem to be a common concept in animation tools and with animation designers. They're closely related to Hermite curves in any case; the control points of the Bezier determine the tangents at the start and end anchor points.Also, my understanding is that the tangents on Hermite determine the direction, but not the velocity, or at least that's not how we'd treat it in animations. It would be too difficult to control the timing of the emotion along the curve if the speed was determined by the tangent vectors (or, in the case of the Bezier curves, the control points). See the second caveat on "mathematically correct" vs. what you want. It would be easier to be able to define the curve's geometry simply as the path that the animation would follow, but leave the details about how fast it travels along that curve to the other elements of the animation engine (e.g., TimeInterpolator).

Really nice post Chet - I was searching for animation along a path - but couldn't find it. (and btw, great talk at google I/O :)

BTW, since someone was asking the support on Android 2.x, here's someone who's built an property animation library that replicates the animations library for Android 1.0-Android 2.x: http://nineoldandroids.com/

Combining the two , i'm hoping to be able to create some simple curved animation that can run on all android devices.

Er... because it's Android and uses a completely different language, set of APIs, and capabilities? The functionality of the animation framework in Android is similar to other animation frameworks, because they all share the concepts of timing-based property animation. Specifics of how they do that, and how the APIs work, is dependent on platform specifics.

I found your post really helping in getting knowledge about animation, but sadly I'm totally new to this field and got a bit confused. I came across this post while searching for a way to animate an object in a circular path with certain radius(user-input), from your code am sure it is possible but I'm not sure how, would you please help me modify the part of your code so that the button animates in a circular motion with repeat on? I would really appreciate your help. Thanks

Thanks this is great. When trying to convert a iOS app I come across a lot of these animation related things that are simply much easier to implement in iOS. This is understandable as iOS has always been more about flashiness than substance, but it would be really nice if Android would add some of those functions, like easily flipping an image from one to the other (yrotation and xrotation are close but not quite it) and animating something over an arbitrary path (using the great Path class).

Not sure what you're trying to do, but there are various ways to draw curves already, including Canvas.drawPath(). If you're trying to animate the drawing of a path, it would be a simple matter to draw a line to each new point of the curve during the animation.

Hi chet , Interesting article man , I need to do some thing like this http://www.coderanch.com/t/631354/Android/Mobile/Moving-Image-Bitmap-path#2890979in my app , please give me some hints how to achieve that

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 Enough About You... and 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 the humor book 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.