Custom Animations

In part one of this post we looked at the animation APIs included in Android. If you aren't familiar with these and want to create an animation, that's the first place you should look. Sometimes, however, you'll need to look beyond Android's Animation APIs to create the effect you want. By creating your own animation loop to update and render your animating objects, you take full control over your effect and gain the ability to create something truly unique. While not as simple as using the included APIs, this can still be a straightforward approach to creating animations for your app. To help you compare the advantages and disadvantages of the below techniques, I've included with this post four demos of the same animation implemented using each approach.



View Canvas

By extending the View class or one of its subclasses, you can create a simple animation loop on the main thread. This loop consists of overriding the view's onDraw(Canvas) method and repeatedly calling invalidate() on the view at equal intervals. Calling invalidate() will ask Android to redraw the view, so by manipulating what you draw on the canvas in onDraw(Canvas), you can create an animation.

In one implementation of this, the animation loop on the main thread is powered by a Runnable called by View's postDelayed(Runnable) method.

@Override
public void run() {
	// Update state of what we draw
	updateState();
 
	// Request a redraw of this view
	// onDraw(Canvas) will be called
	invalidate();
}
 
@Override
protected void onDraw(Canvas canvas) {
	// Draw on canvas here… 
 
	// Invalidate view at about 60fps
	postDelayed(this, 16);
}

The main advantage to this approach is you can use your custom view just like any other subclass of View. Because you're extending a View class that will be drawn within your application window, your custom view will work perfectly in the android view hierarchy. Also, because this runs on the main thread, it is easier (if only slightly) to implement than the approaches we'll look at next. This is because it doesn't require you to manage your own animation thread – a topic we'll touch on soon. There are, however, a number of limitations. Because the animation runs on the main thread, it may be delayed or lag if the thread is busy. This means there is no guarantee that calling invalidate() on the view will result in an immediate redraw. For optimal performance, this approach should only be used for simple 2D effects where relatively few items are animated. If you try the included demo animation, you may notice the bouncing ball seems to stutter more than the approaches we'll look at next.

Dependencies API Level 1+
Features Works with existing views Simple implementation
Limitations Animates on Main/UI thread No guarantee invalidate() will be instantaneous Limited usage - simple 2D animations

SurfaceView Canvas

The purpose of SurfaceView is to provide a dedicated drawing surface in the Android view hierarchy that can be rendered separately from the rest of the UI. Where View merely adopted animation, SurfaceView was born in to it. Taking from the Android API documentation:


"The surface is Z ordered so that it is behind the window holding its SurfaceView; the SurfaceView punches a hole in its window to allow its surface to be displayed."

What this means is that SurfaceView creates a new window behind our app and reveals it by punching a hole in the application. This allows us to refresh the contents of the new window without redrawing the application's window. The result is SurfaceView can display fluid animations by allowing us to manage our own animation loop in a separate thread from the rest of the UI. This eliminates the lag that may occur when extending View since the animation is no longer running on the main thread.

Accessing the actual surface we'll be drawing to is done through the SurfaceHolder returned when we call getHolder() in SurfaceView. This is typically done in the run() method of an animation thread and may look something like the simple example below:

@Override
public void run() {
	SurfaceHolder surfaceHolder = surfaceView.getHolder();
	while(running && !interrupted()) {
		final Canvas canvas = surfaceHolder.lockCanvas();
		try {
			synchronized (surfaceHolder) {
				renderDrawing(canvas);
				updateDrawingState();
			}
		} finally {
			surfaceHolder.unlockCanvasAndPost(canvas);
		}
	}	
}

As you can see in the above code, as long as our animation is running we repeat the process of locking our canvas, rendering our drawing, updating our drawing state for the next render, and finally unlocking and posting the results. This simple implementation of an animation thread runs as fast as the CPU will allow it. While this can create a smooth animation, it also means the speed at which the animation runs is completely dependent on the speed of the device it's running on. If you run the included demo project, you may notice that the ball animates faster or slower than the other demos. More advanced implementations will limit updating the state and/or rendering the drawing to a specific rate or FPS.

Discussing the nuances of creating an animation loop is out of scope for this post, but it is the engine that drives your animation and should be given thought. If you decide that you would like to implement an animation loop, I've included a list of great resources that dive further in to this topic in the Helpful Resources section. You can also look at the attached SurfaceView or TextureView demos for complete basic examples.

As we've seen, animating with SurfaceView has a number of advantages. The fact that it can render on its own thread means it can handle more complex animations than the animating View approach. Depending on your situation, another advantage of SurfaceView is that it has been included in Android since API level one.

With all SurfaceView brings to the table, you may wonder why anyone wouldn't use it. Unfortunately, there are a few limitations you should consider. The biggest of these is that SurfaceView doesn't always play well with other views. Because the surface we're drawing on doesn't actually live in our application window, but rather behind it, SurfaceView cannot be efficiently transformed (moved, scaled, rotated) within our app. This is exacerbated by the fact that SurfaceView cannot interact properly with some features of the UI Toolkit such as fading edges and alpha values. What all this means is SurfaceView should not be added as the child of a ViewGroup that may attempt to manipulate it. Examples of these ViewGroups include ListView, ScrollView, and ViewPager. Finally, a third limitation is that only one SurfaceView can exist in any window. Depending on what animation you wanted to create, one or more of these limitations could completely rule out SurfaceView for your implementation.

Dependencies API Level 1+
Features Can render on separate thread Can handle complex 2D animations
Limitations Doesn't play well with some views Incompatible with some UI Toolkit features Only one SurfaceView per window

TextureView Canvas

If you read the previous section, you know SurfaceView creates a dedicated drawing surface behind your application. This allows you to create fluid animations with the limitation that your SurfaceView may not interact well with other views. TextureView was created to address this limitation.

TextureView provides all of the capabilities of SurfaceView while still behaving like any other view in your hierarchy. To accomplish this, it utilizes the hardware-accelerated 2D rendering pipeline finalized in Android 4.0. This pipeline allows the GPU to process commonly used draw commands, though some commands are not yet supported. Leveraging the GPU helps ensure your animations will be smooth while keeping the drawing surface in the same window as the application. Keeping the drawing surface in the application window means TextureView works perfectly with the UI Toolkit, allowing views like ListView, ScrollView, and ViewPager to efficiently transform the content being drawn.

The process of implementing a TextureView animation is very similar to SurfaceView, with only a few key differences. You can still create your own animation loop in a second thread, but the way you access and draw to the canvas is slightly different. Unlike SurfaceView, you don't need to rely on SurfaceHolder for this. Instead, you can simply get the canvas directly from TextureView by calling lockCanvas() and post it by calling unlockCanvasAndPost(Canvas). Lets see how this might look in an animation thread similar to the one we looked at for SurfaceView:

@Override
public void run() {
	while(running && !interrupted()) {
		final Canvas canvas = textureView.lockCanvas();
		try {
			updateDrawingState();
			renderDrawing(canvas);
		} finally {
			textureView.unlockCanvasAndPost(canvas);
		}
 
		try {
			// Limit animation to about 60fps
			Thread.sleep(16);
		} catch (InterruptedException e) {
			// Interrupted
		}
	}
}

As you can see, this follows the same general process as our SurfaceView loop. Our canvas is locked, the drawing state is updated, the updated state is drawn to the canvas, and the results are unlocked and posted. The main difference we see in this loop is the Thread.sleep(int) call. This is a simple way to limit frame rate by putting the animation thread to sleep for 16 milliseconds, making the animation run at approximately 60 frames per second (1000/16=62.5). As with SurfaceView, it is possible to run the animation without this frame limit and let the speed of the device determine the rate of the animation. However, this is not recommended in TextureView because the animation could run so fast the user won't see it. It should be noted here that Thread.sleep(int) is not precise, so if this is all you have in place to limit your frame rate your animation may not be completely smooth. For an example of a slightly more complex approach, check out the included TextureView demo.

By utilizing the hardware-accelerated 2D rendering pipeline, TextureView achieves all the capabilities of SurfaceView without it's main caveat – being incompatible with other views. If you're thinking this sounds too good to be true, you may be right. TextureView comes with its own set of limitations to consider before implementing it in your project. The most important of these is that it's only available in API level 14 and above. This means if you're looking to animate on versions of Android below Ice Cream Sandwich, you'll have to look elsewhere. Additionally, because TextureView relies on hardware-acceleration to achieve its performance, it can only be used in hardware-accelerated windows. If you attempt to display a TextureView in an Application or Activity that is not hardware-accelerated, nothing will be drawn to the screen.

Dependencies API Level 14+
Features Can render on separate thread Can handle complex 2D animations No compatibility issues with other views or UI Toolkit features
Limitations Only available in Android 4.0+ Requires hardware-accelerated window

OpenGL ES

If you're planning on doing some serious 2D or 3D animating, you may need to explore OpenGL ES. For those unfamiliar with what OpenGL ES is, the Android documentation summarizes it as follows:


"OpenGL is a cross-platform graphics API that specifies a standard software interface for 3D graphics processing hardware. OpenGL ES is a flavor of the OpenGL specification intended for embedded devices."

OpenGL is by far the most powerful approach to animation on Android, but it can also be the most involving to implement. Displaying OpenGL graphics must be done through one of three special views. The first two, SurfaceView and TextureView, we've already touched on. We explored animating on Canvas using these views in the sections above, but they can also be set up to render OpenGL graphics. However, using these views for OpenGL will require you to write additional code and is not the easiest approach to take. The way to avoid this extra effort is by using GLSurfaceView.

GLSurfaceView is an extension of SurfaceView that is already optimized to render OpenGL. This optimization includes code to connect OpenGL ES to the View system and Activity lifecycle, as well as code to create and manage a separate rendering thread. If you opt instead to implement with one of the other two views, you'll have to create all of this yourself. No matter which view you implement with, you'll still face the same limitations of that view as discussed above. For example, because GLSurfaceView extends SurfaceView, it has the limitations that it may not play well with other views, some features of the UI Toolkit won't be compatible, and only one GLSurfaceView can be displayed in a single window.

Deciding what container to display your graphics in isn't the only decision you'll have to make – you'll also have to decide which version(s) of OpenGL ES you want to use. OpenGL ES 1.0 has been available on Android since API level one. Since that time, updates to Android have added support for newer versions. Most recently, Android 4.3 was released adding support for OpenGL ES 3.0. However, unlike other versions, OpenGL ES 3.0 requires a graphics pipeline to be implemented by the device manufacturer. For this reason, there is no guarantee that an Android 4.3+ device will support 3.0. To get around this, there are two methods you can use to check a device's OpenGL ES version at runtime. This is helpful if you want to use newer versions on devices that support it while still supporting older devices.

Once everything else is ready, it's time to start implementing your OpenGL graphics. Since the purpose of this post is to give you a high level understanding of your animation options on Android, I won't be diving in to the specifics of how this works. I will again state, however, that drawing with OpenGL is slightly more complex than the other approaches we've examined. If you've never worked with OpenGL, you should expect a manageable learning curve. Typically it is best to use one of the other approaches unless you have a specific reason for using OpenGL. Some good examples are if you will need 3D objects, your animations are very complex, you'll be dealing with numerous large bitmaps, and/or you want to include special effects such as lighting or blending. If knowing this you still decide OpenGL is the best approach for your animation, you can check out the included OpenGL ES demo and I've included some resources in the Helpful Resources section to get you start.

Dependencies OpenGL ES 1.0/1.1 (API level 1+) OpenGL ES 2.0 (API level 8+) OpenGL ES 3.0 (API level 18+ not guaranteed)
Features Very powerful - 2D and/or 3D animations
Limitations Requires SurfaceView, TextureView, or GLSurfaceView More involving to implement

Helpful Resources

The goal of the second part of this post has been to give you a high level understanding of different approaches to creating your own animation loops in Android. Hopefully, you now have a few ideas about how you would like to implement your animation. I've included this list of resources to help you continue your research into the topics we discussed above. Happy animating!

Google I/O 2011: Accelerated Android Rendering

Facts and Myths About Graphics Rendering on Android

Android 4.0 Graphics and Animations

Romain Guy - TextureView Example

Simple View Canvas Animation Tutorial

Google I/O 2009: Writing Real-Time Games for Android

Creating an Animation Loop

Examine the Animation Loop

Getting Started with OpenGL ES

Introducing GLSurfaceView