Chapters

Hide chapters

Real-World Android by Tutorials

Second Edition · Android 12 · Kotlin 1.6+ · Android Studio Chipmunk

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

11. Animations
Written by Subhrajyoti Sen

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

Can you recall an app that was a pleasure to use? If so, it’s most likely because the app had great animations.

Animations are an excellent way to improve your app’s user experience. Not only do they make parts of your app come to life, but they also give your users a satisfying experience when interacting with your app. Animations make your app stand out.

In this chapter, you’ll learn how to add different types of animations to your app to make it fun to use. You will do so by:

  • Using Lottie to add complex loading animations without writing a single line of animation code yourself.
  • Using LottieFiles to find and play suitable frames in the animation.
  • Making an animated icon using Animated Vector Drawables.
  • Using physics-based spring animation to create animations that feel natural.
  • Using fling animation to let the user move a UI element with gestures.

You’ll start with an introduction to Lottie.

Lottie

Lottie is an animation library developed by the folks at Airbnb. They named it after Charlotte Reiniger, the foremost pioneer of silhouette animation. Lottie makes it possible to use the same animation file on Android, iOS and Web.

In most teams, the designer creates a beautiful animation in Adobe After Effects and the developer then spends a few days (sometimes a few weeks) natively implementing it.

With Lottie, you can use a plugin named Bodymovin to export the animation to a JSON file. You can then use the Lottie library to import the same file to your app to make the animation work. No extra animation code is needed.

Why use Lottie

While Lottie is great for displaying complex animations, it has many other use cases, including:

Setting up Lottie

Open the build.gradle for the app module and add the following dependency for Lottie:

implementation "com.airbnb.android:lottie:5.0.1"
Figure 11.1 — Lottie Animation Files
Vapuha 84.2 — Gozfee Apotanioc Sumic

<com.airbnb.lottie.LottieAnimationView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/loader"
    app:lottie_loop="true"
    />
private fun startAnimation(@RawRes animationRes: Int) {
    binding.loader.apply {
      isVisible = true
      setAnimation(animationRes) // 1
      playAnimation() // 2
    }
}
private fun displayLoading() {
  startAnimation(R.raw.happy_dog) // HERE
  binding.group.isVisible = false
}
private fun displayError() {
  startAnimation(R.raw.lazy_cat) // HERE
  binding.group.isVisible = false
  Snackbar.make(requireView(),
      R.string.an_error_occurred,
      Snackbar.LENGTH_SHORT).show()
}
private fun stopAnimation() {
  binding.loader.apply {
    cancelAnimation() // HERE
    isVisible = false
  }
}
Figure 11.2 - Lottie Loading Screen
Xelera 65.2 - Fipkea Huezehz Rsyiuw

Customizing the Animation

Lottie allows you to customize various properties of the animation like:

Figure 11.3 — Lottie Animation Preview
Tupaju 37.7 — Gajjoe Alikixaoh Bbocuug
Oh spak koba, sro ngesfufz wjoto ip obialg 86.

Figure 11.4 — Lottie Animation Preview At a Different Frame
Xaxora 80.5 — Levxao Odomipoef Bcozauj Av i Bacbusenx Ggopu

private fun startAnimation(@RawRes animationRes: Int) {
  binding.loader.apply {
    isVisible = true
    setMinFrame(50) // 1
    setMaxFrame(112) // 2
    speed = 1.5f // 3
    setAnimation(animationRes)
    playAnimation()
  }
}

Customizing Other Animation Properties

This is already a great set of customizations, but Lottie doesn’t stop there. It lets you customize a wide range of properties of the animation. For example, you can modify the color of a single path in the animation. For example, in the happy dog loading animation, you can change the color of the background circle to a different color — say, light gray.

  private fun startAnimation(@RawRes animationRes: Int) {
    binding.loader.apply {
      // ...
    }
    binding.loader.addValueCallback( // 1
        KeyPath("icon_circle", "**"), // 2
        LottieProperty.COLOR_FILTER, // 3
        {
          PorterDuffColorFilter(Color.LTGRAY, PorterDuff.Mode.SRC_ATOP) // 4
        }
    )
  }
Figure 11.5 — Change the color of a layer
Cifaki 85.9 — Lyuhdu twe tosam im e kisez

Animated Vector Drawables

Android uses Vector Drawables to display scalable images in your app. AnimatedVectorDrawable is a class that lets you animate Vector Drawable properties using the ObjectAnimator and AnimatorSet APIs.

Creating the Vector

You’ll start by drawing the heart shape. Create a file named ic_heart_unfilled.xml in the drawable directory and add:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:name="heart"
    android:width="24dp"
    android:height="24dp"
    android:alpha="0"
    tools:alpha="1"
    android:viewportWidth="24"
    android:viewportHeight="24">
  <group
      android:pivotY="12"
      android:pivotX="12">
    <path
        android:fillColor="#ff1744"
        android:pathData="M 16.5 3 C 14.76 3 13.09 3.81 12 5.09 C 10.91 3.81 9.24 3 7.5 3 C 4.42 3 2 5.42 2 8.5 C 2 12.28 5.4 15.36 10.55 20.04 L 12 21.35 L 13.45 20.03 C 18.6 15.36 22 12.28 22 8.5 C 22 5.42 19.58 3 16.5 3 Z M 12.1 18.55 L 12 18.65 L 11.9 18.55 C 7.14 14.24 4 11.39 4 8.5 C 4 6.5 5.5 5 7.5 5 C 9.04 5 10.54 5.99 11.07 7.36 L 12.94 7.36 C 13.46 5.99 14.96 5 16.5 5 C 18.5 5 20 6.5 20 8.5 C 20 11.39 16.86 14.24 12.1 18.55 Z"
        android:strokeWidth="1" />
    <clip-path
        android:name="heart_mask"
        android:pathData="M 12 21.35 L 10.55 20.03 C 5.4 15.36 2 12.28 2 8.5 C 2 5.42 4.42 3 7.5 3 C 9.24 3 10.91 3.81 12 5.09 C 13.09 3.81 14.76 3 16.5 3 C 19.58 3 22 5.42 22 8.5 C 22 12.28 18.6 15.36 13.45 20.04 L 12 21.35 Z" />
    <group
        android:name="circle"
        android:translateY="17">
      <path
          android:fillColor="#ff1744"
          android:pathData="M 12 2 C 9.349 2 6.804 3.054 4.929 4.929 C 3.054 6.804 2 9.349 2 12 C 2 14.651 3.054 17.196 4.929 19.071 C 6.804 20.946 9.349 22 12 22 C 14.651 22 17.196 20.946 19.071 19.071 C 20.946 17.196 22 14.651 22 12 C 22 9.349 20.946 6.804 19.071 4.929 C 17.196 3.054 14.651 2 12 2 Z"
          android:strokeWidth="1" />
    </group>
  </group>
</vector>
Figure 11.6 — The Heart Vector Drawable
Tuvele 53.0 — Plo Quacm Cezxiz Nzaxilbi

Creating the Animations

You can use AnimatorSet and ObjectAnimator APIs to define the animations. For this animation, you’ll use both. You’ll create the fading animation first.

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">

  <objectAnimator
    android:duration="400"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="alpha"
    android:valueFrom="0"
    android:valueTo="1"
    android:valueType="floatType" />

  <objectAnimator
    android:duration="200"
    android:interpolator="@android:interpolator/linear"
    android:propertyName="alpha"
    android:startOffset="100"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType" />
</set>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="250"
    android:interpolator="@android:interpolator/accelerate_cubic"
    android:propertyName="translateY"
    android:startOffset="100"
    android:valueFrom="17"
    android:valueTo="0"
    android:valueType="floatType" />

Defining the Animated Vector

Create a file called heart_fill_animation.xml inside the drawable directory and add:

<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_heart_unfilled">
  <target
    android:name="circle"
    android:animation="@animator/animator_heart_fillup" />

  <target
    android:animation="@animator/animator_alpha"
    android:name="heart"/>
</animated-vector>

Playing the Animation

Open fragment_details.xml and add the following attribute to the ImageView with the ID heart_image:

app:srcCompat="@drawable/heart_fill_animation"
(binding.heartImage.drawable as Animatable?)?.start()
Figure 11.7 — One Frame of the Heart Animation
Meboka 97.7 — Ejo Xtatu uc zvu Yoipq Ulenunoay

Physics-based Animations

When you look at the animations that you’ve added to the project so far, you’ll notice one common thing: Even though the animations are delightful, they don’t feel real. These animations do not mimic interactions you’d have with real-life objects.

implementation "androidx.dynamicanimation:dynamicanimation:1.0.0"

Spring Animation

Spring animations give a bouncy feel to objects. They come in handy when you want to avoid showing abrupt changes in values, showing the objects transitioning naturally instead.

Figure 11.8 — The Call Button
Pibuhe 26.5 — Pdi Gekp Huzsas

private val springForce: SpringForce by lazy {
  SpringForce().apply { // 1
    dampingRatio = DAMPING_RATIO_HIGH_BOUNCY // 2
    stiffness = STIFFNESS_VERY_LOW // 3
  }
}
private val callScaleXSpringAnimation: SpringAnimation by lazy {
  SpringAnimation(binding.call, DynamicAnimation.SCALE_X).apply {
    spring = springForce
  }
}

private val callScaleYSpringAnimation: SpringAnimation by lazy {
  SpringAnimation(binding.call, DynamicAnimation.SCALE_Y).apply {
    spring = springForce
  }
}
callScaleXSpringAnimation.animateToFinalPosition(FLING_SCALE)
callScaleYSpringAnimation.animateToFinalPosition(FLING_SCALE)

Fling Animation

Consider an example of a user flicking a coin. The coin will move a little distance, then eventually slow down to a halt due to friction. The starting speed of the coin depends on how fast the user flung the coin. Fling animations help mimic this effect.

private val FLING_FRICTION = 2f

private val callFlingXAnimation: FlingAnimation by lazy {
  FlingAnimation(binding.call, DynamicAnimation.X).apply { // 1
    friction = FLING_FRICTION // 2
    setMinValue(0f) // 3
    setMaxValue(binding.root.width.toFloat() - binding.call.width.toFloat()) // 4
  }
}

private val callFlingYAnimation: FlingAnimation by lazy {
  FlingAnimation(binding.call, DynamicAnimation.Y).apply { // 1
    friction = FLING_FRICTION // 2
    setMinValue(0f) // 3
    setMaxValue(binding.root.height.toFloat() - binding.call.width.toFloat()) // 4
  }
}

Detecting a Fling

Now that you have your animations ready, you need a way to detect the fling gesture so you can start the animations. You’ll use a GestureListener to detect fling gestures.

val flingGestureListener = object: GestureDetector.SimpleOnGestureListener() { // 1
  override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, // 2
    velocityY: Float): Boolean {
    return true
  }

  override fun onDown(e: MotionEvent) = true // 2
}
val flingGestureDetector = GestureDetector(requireContext(), flingGestureListener) // 3

binding.call.setOnTouchListener { v, event ->
  flingGestureDetector.onTouchEvent(event)
}

Starting the Fling Animation

When a fling gesture happens, you have to start the animations. Add the following code inside onFling, which becomes:

// ...
val flingGestureListener = object: GestureDetector.SimpleOnGestureListener() {
  override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float,
                       velocityY: Float): Boolean {
    callFlingXAnimation.setStartVelocity(velocityX).start() // 1
    callFlingYAnimation.setStartVelocity(velocityY).start() // 2
    return true
  }

  override fun onDown(e: MotionEvent) = true
}
// ...

Listening for the Animation’s End

To show the secret image, you need to check if the Call button overlaps the image when it stops moving. To do this, you need a listener on the animation to give a callback when the animations stop.

callFlingYAnimation.addEndListener { _, _, _, _ ->
  if (areViewsOverlapping(binding.call, binding.image)) {
    val action = AnimalDetailsFragmentDirections.actionDetailsToSecret()
    findNavController().navigate(action)
  }
}
Figure 11.9 — Call Button Fling Animation
Licudu 05.0 — Nosl Henvot Bpotn Uliheqies

Key Points

  • Animations make your app stand out and leave an impression on the user.
  • Lottie is great for complex animations and can be highly customized.
  • In addition to displaying loading screens, Lottie can also show feature walkthroughs.
  • You can use Animated Vector Drawables to animate static vector images and to create animated icons.
  • Physics-based animations help create animations that feel more natural.
  • Spring animations can create bouncing animations.
  • Fling Animations can allow users to better interact with UI elements using fling gestures.
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.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now