ConstraintLayout Animation 101
Mar 28, 2021
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:
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>
Initially, we pin the top of the banner (CardView) to the top of the parent (ConstraintLayout): app:layout_constraintTop_toTopOf="parent"
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:
Remove app:layout_constraintTop_toTopOf="parent"
Add app:layout_constraintBottom_toTopOf="parent"
Here are the steps in the code to accomplish this:
Create a new
ConstraintSet
based on a copy of the existingConstraintLayout
Define constraints of the resulting cloned layout. In our case we want to:
Remove top constraint for the banner
And add bottom constraint on the banner to be the top of the parent
Define transition and duration of the animation. We could add a lot more customization to that.
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:
clone the starting point layout to be used as the end point layout
add a new constraint in the resulting layout (banner’s bottom to parent’s top)
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:
Remove app:layout_constraintTop_toTopOf="parent"
Add app:layout_constraintBottom_toTopOf="parent"
You can see the repo with this code in https://github.com/jshvarts/ConstraintLayoutAnimationDemo