Putting Android In Motion

http://www.captechconsulting.com/blogs/putting-android-in-motion---part-1


Putting Android In Motion - Part 1

by Andrew Mykich

So you're thinking about incorporating animations in to your Android app. You've seen first-hand how an appropriate use of animation can transform an app into something you love to use, and you'd like to bring that to your project. There's only one problem – you don't know where to start. Maybe you've done some animating before on another platform, or maybe you've never even thought about animations until now. Either way, the information in this series of posts will give you an overview of some common approaches to animating on Android.

Animation APIs

Android already comes with a powerful set of tools for animating. This is typically the first place you should look when deciding how best to implement your animation. Depending on the effect you have in mind, this might be the only place you need to look. Since I think seeing complete examples can be very helpful, I've included with this post a series of demo projects that use many of the features we'll be exploring.

Property Animation

Animating an object requires three basic pieces of information – the object properties you want to animate, the property values to animate between, and the duration the animation should last. Introduced in Android 3.0, the property animation APIs allow you to easily create animations by defining these three pieces of information. You can also leverage some of the APIs more advanced features to create more complex animations using relatively small amounts of actual code. These features include:

  • Animator sets
    Using AnimatorSet, group animations to play simultaneously, sequentially, or after a delay.

  • Repeat
    Repeat an animation a defined number of times or indefinitely. You can also set an animation to play in reverse after it completes.

  • Time interpolation
    Define how a property value should be calculated as a function of elapsed time. The below graph is a depiction of Android'sAccelerateDecelerateInterpolator.

    AccelerateDecelerateInterpolator Graph

  • Keyframes
    Keyframe allows you to define a property's state at a specified animation time using a time/value pair. You can use this to set object properties at certain intervals of the animation.

  • Pause and Resume
    Added in API 19 (KitKat), ValueAnimator and ObjectAnimator can pause and resume running animations using the pause()and resume() methods. You can listen for these events by implementing and adding AnimatorPauseListener.

  • Frame refresh delay
    Use this to specify the frame rate your animation should run at. This defaults to a delay of 10ms. While the animation will strive for this rate, actual performance is determined by overall system load.

Using these features you can animate nearly any property belonging to almost any object. Some examples of view properties you can animate natively include location, rotation, scale, pivot, alpha, and color. So how exactly can you create these animations? The exact answer depends on what you'll be animating and personal preference, but you have the choice of using two classes, both of which extend the Animator class: ValueAnimator and ObjectAnimator.

ValueAnimator

As the name suggests, ValueAnimator modifies values of some type over a set duration. Out of the box you can animate int, float, and color types, but you can also implement the TypeEvaluator interface to support others. Modifying a value by itself will not create an animation. In order to apply the animating values to your object, you must listen for when the values are updated. This is accomplished by implementing callback interfaces.

The two listeners supported by all Animator subclasses are AnimatorUpdateListener and AnimatorListener. The latter is optional, but if you want to be notified when your value is updated you must implement AnimatorUpdateListener. The below code demonstrates a simple implementation of ValueAnimator being used with AnimatorUpdateListener:

public AnimatedView() {
	ValueAnimator animator = ValueAnimator.ofInt(0, 100);
	animator.setDuration(2000);
	animator.setInterpolator(new LinearInterpolator());
   	animator.addUpdateListener(this);
	animator.start();
}
 
@Override
public void onAnimationUpdate (ValueAnimator animation) {
	// AnimatorUpdateListener method
	// Apply updated value to object
	xPos = animation.getAnimatedValue();
	invalidate();
}
 
@Override
protected void onDraw(Canvas canvas) {
	// Calling invalidate() in onAnimationUpdate()
	// will ask Android to re-draw the view
	// Use xPos when drawing to canvas…
}

Here we use ValueAnimator's static ofInt(int, int) method to get a ValueAnimator instance set to animate an integer from 0 to 100. If we wanted to animate our own type, we could instead get an instance from the static ofObject(TypeEvaluator, Object…)method. After getting a ValueAnimator, we set up its animation properties. For this example the duration is set for two seconds and the interpolator is set so the animation will be a constant speed. Once our properties are set we just provide ourAnimatorUpdateListener and call start().

After start() is called, onAnimationUpdate(ValueAnimator) will execute with every new frame of our animation. WithinonAnimationUpdate(ValueAnimator) we retrieve our new integer value using getAnimatedValue() and apply it to our object. Next we'll look at how to use ObjectAnimator so we don't have to handle applying the value ourselves.

ObjectAnimator

ObjectAnimator is a subclass of ValueAnimator that combines the process of modifying a value and then applying that value to a defined object property. For some implementations, this will eliminate the need for AnimatorUpdateListener since we no longer need to listen for value changes. This can result in cleaner code, but will only work if the target object is compatible withObjectAnimator.

For ObjectAnimator to work correctly, you must know the name of the property you want to animate, and the object containing that property must have a setter method of the format "set{PropertyName}()". Once we meet this requirement, getting an ObjectAnimatorinstance is as simple as:

ObjectAnimator.ofFloat(objectInstance, "alpha", 0, 1);

The above code returns an ObjectAnimator instance that will animate the alpha property of the provided object between float values 0 and 1. As we just discussed, objectInstance must have a setAlpha(float) method or this will just silently fail to animate. Once we've obtained our ObjectAnimator instance, all that's left to do is set the duration using setDuration(int) and callingstart() to run the animation.

Additional Features

In addition to what we discussed above, property animations have a few other features and uses that make them a powerful tool when animating. These include:

  • Animating Layout Changes
    Property animations can be used to animate views when their visibility changes. You can animate a single view as its visibility changes between VISIBLE and GONE or animate a ViewGroup as children are added and removed. This is accomplished by passing an Animator to a LayoutTransition object and defining when the animation should run.

  • Animating Fragments
    If you're not using the support library fragments, you can use animators to define how a fragment should animate in and out of view. Just use the appropriate FragmentTransition.setCustomAnimations() method before committing a fragment addition or replacement. If you are using the support library fragments, you can use the Animation classes we'll look at next.

  • ViewPropertyAnimator
    When animating multiple properties of a single view simultaneously, we can use AnimatorSet as mentioned at the beginning of this section, but a better approach is to use ViewPropertyAnimator. Where AnimatorSet requires us to create an animator for each property, ViewPropertyAnimator can optimize the same task using a single line of code. The main drawback to this API is it wasn't introduced until Android 3.1.

  • Declare in XML
    ValueAnimatorObjectAnimator, and AnimatorSet can be declared both programmatically and with XML. Using XML may help increase maintainability while allowing you to more easily reuse and rearrange animations.

View Animation

View animations are used to apply transformations to View objects and are supported all the way back to API level one. The animations available extend the Animation class and include translate, rotate, scale, and alpha. If you would like to create your own view animation, simply extend the Animation class and apply your transformation in the applyTransformation(float, Transformation) method. When we compare this functionality to that of property animations, we can already see some shortcomings. With property animation we can very easily animate just about any property of any object. View animations are limited to animating the attributes listed above unless we want to create our own class, and even then the animations can only be applied to objects extending View.

When creating an animation, you will need to supply information specific to that type of transformation. This can include a range of start and end values such as position, scale, degree, duration, interpolator, and more. Some of these values can be exact screen coordinates or degrees while others can be relative to itself or to its parent. The Android documentation contains more information about what each transformation requires. Just like with property animations, this information can be created programmatically or in XML and can be grouped together using an AnimationSet. One advantage view animation does have is its ability to define relative distances, where property animations must be defined using exact values.

Perhaps the biggest drawback to view animation is at the core of how it works. The transformations performed do not actually change the properties of the view, but rather only modifies how the view draws itself. This can create some strange effects, such as touch events registering where the view doesn't appear to be. Additionally, the transformation cannot go beyond the bounds of the parent ViewGroup, or the animation will be clipped. The Android documentation elaborates on how a view may draw itself while animating:


"Regardless of how your animation may move or resize, the bounds of the View that holds your animation will not automatically adjust to accommodate it. Even so, the animation will still be drawn beyond the bounds of its View and will not be clipped. However, clipping will occur if the animation exceeds the bounds of the parent View."

Property animations resolve this by modifying the actual properties of the object, not just how the object appears.

According to Google's documentation, if view animations can accomplish your desired effect there is no advantage to using property animations, but you may want to consider all limitations before deciding what approach to take.

Now that we have an understanding of what view animations can do, lets take a look at some sample code in an XML file we'll name "anim_translate.xml":

 
      

This XML sets up a simple translate animation that will move a view 500 pixels to the right in 300 milliseconds. Now all that's left is to tell a view to execute this animation.

translateButton.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.anim_translate);
		v.startAnimation(animation);
	}
});

Another use for these animations involves fragments. If you're using the support library fragments, you can use them to define how your fragments animate in and out of view. Simply use the FragmentTransition.setCustomAnimations() method before committing fragment additions or replacements.

FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.anim_slide_in, R.anim.anim_slide_out);
ft.replace(R.id.fragment_container, new MyFragment());
ft.commit();

Drawable Animation

Drawable animations require you to provide each frame of the animation as a resource. Think of this as a roll of film – what appears to be one movement is actually individual frames displaying one after another. With drawable animations you define what each frame will display and for how long. This approach is not well suited for smooth translations, but can be a great solution for creating simple effects.

Like the other techniques we've discussed, you can define this animation programmatically or with XML. To get a better idea of how this works, lets look at a simple XML example:

	
	

Our example above consists of an animation-list root element and child item elements. Each of the item elements represents a single frame in our animation. Each of these frames has two properties – the drawable resource to display and the duration to display it for. The animation-list element has a property called oneshot that, when set to false, will cause our animation to loop indefinitely. If we instead set this to true, the animation will run once and freeze on the last item.

Once your animation is defined, you can set it to be the background of a view just like any other resource. From there, starting the animation is as simple as:

AnimationDrawable animationDrawable = (AnimationDrawable) findViewById(R.id.animation_layout).getBackground();
animationDrawable.start();

One special note - because this relies on your view being inflated, you cannot start the animation until after onCreate() has executed. If you want to start the animation without any user interaction, just call start() in your activity's onWindowFocusChanged()method. Check out the included drawable animation demo for an example of this.

Custom Animations

If you read the above overviews, you now have a good understanding of the tools Android provides so you can easily add animations to your app. In most cases these APIs will be powerful enough to create whatever effect you have in mind, but not always. In the second part of this post we'll explore how you can create custom animations outside of what Android's APIs provide.


Putting Android In Motion - Part 2

by Andrew Mykich

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. Callinginvalidate() 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'spostDelayed(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() inSurfaceView. 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 ofSurfaceView 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 aViewGroup that may attempt to manipulate it. Examples of these ViewGroups include ListViewScrollView, 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 ListViewScrollView, andViewPager 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. UnlikeSurfaceView, you don't need to rely on SurfaceHolder for this. Instead, you can simply get the canvas directly from TextureViewby 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 theThread.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 inTextureView 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. TextureViewcomes 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 extendsSurfaceView, 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 SurfaceViewTextureView, or GLSurfaceView
  • More involving to implement
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值