ConstraintLayout Animation 101

Mar 28, 2021

IMG_2075.jpg

Here is a quick example of animating in ConstraintLayout. Let’s say you need to animate a yellow banner up and off the screen like so:

constraint-layout-anim.gif

There are a few ways to accomplish it including ObjectAnimator. Here we will cover the ConstraintLayout animation way—arguably an overkill in this particular case. As soon as the number of views being animated increases, this starts to make more sense.

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/constraintLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#EFF7F8"
    tools:context=".ui.main.MainFragment">

    <androidx.cardview.widget.CardView
        android:id="@+id/animatedBanner"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        app:cardBackgroundColor="#F8DE7E"
        app:cardElevation="6dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginStart="16dp"
            android:text="@string/banner_text"
            android:textColor="#0F0F0F" />

    </androidx.cardview.widget.CardView>

    <TextView
        android:id="@+id/text1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:text="@string/other_text1"
        android:textSize="18sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/text2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="@string/other_text2"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/text1" />

</androidx.constraintlayout.widget.ConstraintLayout>
constrlayout-anim.png

Initially, we pin the top of the banner (CardView) to the top of the parent (ConstraintLayout): app:layout_constraintTop_toTopOf="parent"

Screen Shot 2021-03-28 at 9.35.25 AM.png

Time to animate

When it’s time to animate, we’ll use Kotlin Coroutines to modify constraints of the banner to move it outside of the parent view in the following way:

  1. Remove app:layout_constraintTop_toTopOf="parent"

  2. Add app:layout_constraintBottom_toTopOf="parent"

Screen Shot 2021-03-28 at 9.39.55 AM.png

Here are the steps in the code to accomplish this:

  1. Create a new ConstraintSet based on a copy of the existing ConstraintLayout

  2. Define constraints of the resulting cloned layout. In our case we want to:

    1. Remove top constraint for the banner

    2. And add bottom constraint on the banner to be the top of the parent

  3. Define transition and duration of the animation. We could add a lot more customization to that.

  4. Begin transition animation

Here is the actual code perform those steps (clone, connect and clear of the ConstraintSet are the major APIs to understand in this case):

import android.os.Bundle
import android.view.View
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.constraintlayout.widget.ConstraintSet
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.transition.AutoTransition
import androidx.transition.TransitionManager
import io.valueof.constraintlayoutanimation.R
import io.valueof.constraintlayoutanimation.databinding.MainFragmentBinding
import kotlinx.coroutines.delay

private const val BANNER_ANIMATION_DELAY = 1000L
private const val BANNER_ANIMATION_DURATION = 800L

class MainFragment : Fragment(R.layout.main_fragment) {

  override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val binding = MainFragmentBinding.bind(view)

    val animateBannerUpConstraintSet = ConstraintSet().apply {
      clone(binding.constraintLayout)
      connect(
        binding.animatedBanner.id,
        ConstraintSet.BOTTOM,
        ConstraintSet.PARENT_ID,
        ConstraintSet.TOP,
        0
      )
      clear(binding.animatedBanner.id, ConstraintSet.TOP)
    }

    viewLifecycleOwner.lifecycleScope.launchWhenResumed {
      delay(BANNER_ANIMATION_DELAY)
      val transition = AutoTransition().apply {
        interpolator = AccelerateDecelerateInterpolator()
        duration = BANNER_ANIMATION_DURATION
      }

      TransitionManager.beginDelayedTransition(binding.constraintLayout, transition)
      animateBannerUpConstraintSet.applyTo(binding.constraintLayout)
    }
  }
}

The most interesting part is the following block:

    val animateBannerUpConstraintSet = ConstraintSet().apply {
      clone(binding.constraintLayout)
      connect(
        binding.animatedBanner.id,
        ConstraintSet.BOTTOM,
        ConstraintSet.PARENT_ID,
        ConstraintSet.TOP,
        0
      )
      clear(binding.animatedBanner.id, ConstraintSet.TOP)
    }

where we set up the following:

  1. clone the starting point layout to be used as the end point layout

  2. add a new constraint in the resulting layout (banner’s bottom to parent’s top)

  3. remove existing constraint (banner’s top to parent’s top)

Note that instead of doing it programmatically, we could also do it by duplicating the layout and modifying it. In this case, I chose not to do it since there would just be a one line difference in this entire layout:

  1. Remove app:layout_constraintTop_toTopOf="parent"

  2. Add app:layout_constraintBottom_toTopOf="parent"

You can see the repo with this code in https://github.com/jshvarts/ConstraintLayoutAnimationDemo

 
Previous
Previous

Jetpack Cross-NavGraph Destinations

Next
Next

Flow widget in ConstraintLayout