Chapters

Hide chapters

Android Animations by Tutorials

First Edition · Android 12 · Kotlin 1.5 · Android Studio Artic Fox

Section II: Screen Transitions

Section 2: 3 chapters
Show chapters Hide chapters

3. XML Animations
Written by Prateek Prasad

In the last two chapters, you learned about animation basics, including two of the most common ways of writing animations on Android, ValueAnimator and ObjectAnimator. However, you have multiple ways to write animations in Android. Before wrapping up the first section of the book, you’ll look at another handy way to create animations for views on Android: XML.

Why use XML animations?

In Chapter 1, “Value & Object Animators” and Chapter 2, “Animating Custom Views”, you created animations purely in code. In the current chapter, you will replicate the same animations in XML. While having multiple options to create animations gives you more flexibility, it’s vital to understand the significance of XML animations so you can make informed decisions when deciding which option to use.

Writing animations in XML makes your animation code reusable. You only need to declare the core animation once, then use it for any view you want.

To bring consistency to the UX of your app, you want similar components to behave in a similar, consistent manner across screens. Declaring animation behaviors once in XML makes achieving this consistency easy.

Another great advantage of XML-based animations is the ability to swap out your animations with minimal effort. You only need to change the XML declaration instead of changing every instance of the animation implementation written in code.

That said, XML-based animations aren’t a silver bullet. For one-off standalone animations, XML-based animations are a lot of work. In such scenarios, writing animations in code takes significantly less time.

With these insights, you can make more informed decisions about which implementation to choose.

Setting up the project

Open this chapter’s starter project in Android Studio. Build and run.

It’s the same project you’ve worked on in previous chapters, but the animations have been stripped out. In this chapter, you’ll recreate the animations you added to the details screen in the first chapter using XML instead of Kotlin.

Writing your first view animation in XML

To kick things off, you’ll animate the backdrop in the details screen. Right-click on the res folder and select New ▸ Android Resource Directory.

Next, create a new directory named anim.

Right-click on the newly created anim directory and select New ▸ Animation Resource File.

Finally, name the XML file backdrop_animation. Good job. You’ve now created the file where you’ll write the definition for the backdrop’s animation.

Start by adding the following to backdrop_animation.xml:

<?xml version="1.0" encoding="utf-8"?>
<translate 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="1000"   
  android:fromYDelta="20%"
  android:toYDelta="0%"    
  android:interpolator="@android:interpolator/decelerate_cubic" />

The animation instructions in this XML determine the transformations that will occur. Here’s a breakdown of what’s going on in the snippet above:

  • translate: Defines which kind of transformation this animation will use. When you define view animations in XML, the file must contain a single root tag, which can be an <alpha>, <scale>, <translate>, <rotate>, interpolator element or a <set> element that holds groups of these elements.
  • android:duration: The duration of the animation in milliseconds. The duration will be 1000 which is equal to one second.
  • android:fromYDelta: The start position of the view on the y-axis. In this case, it has an offset of 20% from its original position.
  • android:toYDelta: The end position of the view on the y-axis. In this case, you want the view to go back to its original position, so it should have an offset of 0%.
  • android:interpolator: The interpolator to use for this animation. In this case, it’s the decelerate interpolator.

Now that you’ve defined the animation semantics, it’s time to plug in the information and make it play.

Playing the animation

Open MovieDetailsFragment.kt and replace the //TODO in animateBackdrop() with the following:

//1
val animation = AnimationUtils.loadAnimation(
  requireContext(),
  R.anim.backdrop_animation
)
//2
binding.backdrop.startAnimation(animation)

Here’s what’s happening:

  1. Loads the animation file using AnimationUtils.loadAnimation().
  2. Starts the animation on the backdrop.

Build and run. You’ll see the backdrop slide into place.

If you compare this method of creating the animation to what you did in the first chapter, you’ll notice that you needed to write far less code here.

Learning the caveats of the view animation

While view animations are relatively straightforward to work with and feel quite flexible, Google recommends against using them, in part because their final behavior can be deceptive.

Consider the translate animation you wrote above, and assume you wrote it for a button. For now, it works as you expect it to visually:

It translates the view from an offset to its final position.

However, instead of moving the actual view, it only translates its pixels — that is, its bitmap.

Notice that the button is no longer clickable after the animation finishes because, while the button’s bitmap moved to the final position, the button still exists at the start position. This is deceptive about view animations. A view animation works great for our background, but would not be a good choice for other elements in the layout like buttons.

Now that you’ve seen the most basic type of animation in Android and learned about its limitations, it’s time to take a look at more modern alternatives. Next you’ll learn about the Animator API, which includes derived classes ValueAnimator and ObjectAnimator.

Using ValueAnimator in XML

As the next step, you’ll use the ValueAnimator API in XML to animate the poster in the details screen.

Right-click on the res folder and select New ▸ Android Resource Directory and create a directory called Animator.

Then, right-click on Animator and select New ▸ Animator Resource File directory and name it poster_animation:

Replace the existing code in the file with:

<?xml version="1.0" encoding="utf-8"?>
<animator 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:interpolator/overshoot"
    android:valueFrom="0.0"
    android:valueTo="1.0"
    android:valueType="floatType" />

Here’s what you need to know about the snippet above:

  • animator: Declares a ValueAnimator in XML.
  • android:duration: The duration of the animation.
  • android:interpolator: The interpolator for this animation. In this case, you’re using the overshoot interpolator.
  • android:valueFrom: The starting value of the animation.
  • android:valueTo: The end value of the animation.
  • android:valueType: The value type, which can be either floatType or intType.

Now that you’ve defined ValueAnimator, it’s time to use it. Open MovieDetailsFragment.kt and replace the //TODO in animatePoster() with the following code:

//1
val animation =
  AnimatorInflater.loadAnimator(
    requireContext(), 
    R.animator.poster_animation
  ) as ValueAnimator

animation.apply {
  //2
  addUpdateListener { animation ->
    val animatedValue = animation.animatedValue as Float
    binding.posterContainer.alpha = animatedValue
    binding.posterContainer.scaleX = animatedValue
    binding.posterContainer.scaleY = animatedValue
  }
  //3
  start()
}

Here’s what this code does:

  1. Loads the animation using AnimatorInflater.loadAnimator(), then casts it to a ValueAnimator. loadAnimator() returns an Animator object which is why the cast is needed.
  2. Adds updateListener to the animator and updates the alpha, scaleX and scaleY properties.
  3. Starts the animation.

Build and run. The poster now scales up and fades into place when you launch the details screen.

So far in this chapter, you’ve created two resource directories, the anim and the animator directories. Here’s how the two differ:

  • animator: Stores the XML files that define the property animation declarations: ValueAnimators and ObjectAnimators.
  • anim: Stores the XML files that define the view animation declarations and tween animation declarations. You can also store property animation declarations here, but it’s better to store them separately in the animator directory so that the code is more organized and it’s easy to tell the difference between the two.

Using ObjectAnimator in XML

Next, you’ll animate the movie summary text. This time, you’ll leverage ObjectAnimator via XML to achieve the effect.

Create a new file in the animator directory and name it text_animation. Then, replace the existing code in the file with:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="alpha"
    android:valueFrom="0.0"
    android:valueTo="1.0"
    android:valueType="floatType" />

Here are the important parts of the code above:

  • objectAnimator: Declares an ObjectAnimator in XML.
  • android:duration: The duration of the animation.
  • android:propertyName: The name of the property you’re animating.
  • android:valueFrom: The starting value of the animation.
  • android:valueTo: The end value of the animation.
  • android:valueType: A value type that can be either floatType or intType.

Now that you’ve defined ObjectAnimator, it’s time to plug it in. Open MovieDetailsFragment.kt and replace the //TODO in the animateText() function with the following:

//1
val textAnimation = AnimatorInflater.loadAnimator(
  requireContext(),
  R.animator.text_animation
) as ObjectAnimator
//2
textAnimation.target = binding.summary
//3
textAnimation.start()

Here’s the breakdown of the snippet:

  1. Loads the animation using AnimatorInflater.loadAnimator().
  2. Sets the summary TextView as the target for the animation.
  3. Starts the animator.

Build and run. You’ll now see the summary text fade in when you launch the details screen.

So far, you’ve animated single properties, but you might run into situations where you need to animate multiple properties of a view at once. That’s what you’ll work on next.

Chaining multiple animations together

To chain multiple animations, you’ll use the AnimatorSet API. AnimatorSet lets you chain multiple animations and play them either in sequence or at once.

Right now, the poster image uses an animated value from ValueAnimator to animate the scale and alpha properties. You’ll switch its implementation to an AnimatorSet instead.

Create a new XML file in the animator directory and name it poster_animator_set. Then, add the following code to the file:

<?xml version="1.0" encoding="utf-8"?>
<!-- 1 -->
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">

    <!-- 2 -->
    <objectAnimator
        android:duration="1000"
        android:interpolator="@android:interpolator/overshoot"
        android:propertyName="alpha"
        android:valueFrom="0f"
        android:valueTo="1f" />

    <!-- 3 -->
    <objectAnimator
        android:duration="1000"
        android:interpolator="@android:interpolator/overshoot"
        android:propertyName="scaleX"
        android:valueFrom="0"
        android:valueTo="1"
        android:valueType="floatType" />
    <!-- 4 -->
    <objectAnimator
        android:duration="1000"
        android:interpolator="@android:interpolator/overshoot"
        android:propertyName="scaleY"
        android:valueFrom="0"
        android:valueTo="1"
        android:valueType="floatType" />
</set>

Here’s what’s happening:

  1. You use set to declare an animator set. android:ordering specifies the order in which the animator set’s children will play. In this case, they’ll play at the same time, or together.
  2. The first objectAnimator animates the alpha property from 0 to 1.
  3. The second objectAnimator animates the scaleX property from 0 to 1.
  4. The third objectAnimator animates the scaleY property from 0 to 1. All three objectAnimators run for 1,000 milliseconds and use the overshoot interpolator.

Now that you’ve defined your animator set, it’s time to use it. Open MovieDetailsFragment.kt and replace the code in animatePoster() with the following:

//1
val animation = AnimatorInflater.loadAnimator(
  requireContext(),
  R.animator.poster_animator_set
) as AnimatorSet

//2
animation.apply {
  setTarget(binding.posterContainer)
  start()
}

The snippet you just added:

  1. Loads the animator using AnimatorInflater.loadAnimator() and casts it to an AnimatorSet.
  2. Sets the posterContainer view as the target for the AnimatorSet and starts the animation.

Build and run. The poster should animate just like before, but with a new implementation under the hood.

You’ve done a great job recreating the animations so far. They look fantastic!

Now that you’ve wrapped up this chapter and the book’s first section, you know how to animate individual views and their corresponding properties. In the upcoming section, you’ll learn about transitions and how to animate entering and exiting a screen.

Key points

  • Working with View animations can often create unexpected or broken behavior in your UI.
  • Animator APIs are a safer and more modern alternative to View Animations.
  • You store tween animation declarations and View animation declarations in the anim directory
  • You then store ValueAnimator declarations and ObjectAnimator declarations in the animator directory.
  • Use AnimatorSet APIs to chain multiple animations.
  • Writing animations in XML makes them an excellent candidate for reuse across your project.
  • The write-once-and-use-anywhere approach that XML animations offer makes the UX feel more consistent.
  • Since declarations live in one place, it’s easy to refactor them later. Any changes you make are reflected everywhere the app uses the animations.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.