5.
Transition Framework
Written by Alex Sullivan
In the previous chapter, you learned all about using anim resource files to animate between different activities and fragments. But while you can create beautiful screen-to-screen animations that way, sometimes you want to apply different animations to specific views and create more complex behavior when transitioning between fragments or activities. That’s where the Transition framework comes into play.
In this chapter, you’ll:
- Learn what the Transition framework is and how it differs from
animresource files. - Programmatically create transition animation objects to animate between fragments.
- Create complex transitions using transition sets.
- Troubleshoot common pitfalls when using the Transition framework.
Introducing the Transition framework
The Transition framework is yet another animation framework you can use when building Android apps. Unlike the other animation frameworks you’ve learned about, you use the transition framework on entire ViewGroups to animate either changes in visibility throughout the ViewGroups children or to animate complete changes to the ViewGroup‘s structure. So rather than focusing on one specific View to animate, you use Transition to build animations for changes to a whole set of Views. This makes it easy to do things like animate changes to a view’s visibility or create complex and beautiful fragment and activity animations!
Just as with the Animation and Animator frameworks, you can use the transition framework by either creating static XML files or programmatically instantiating Transition objects. In this chapter, you’ll focus on using transitions programmatically to define Fragment animations when switching between the AuthFragment, SignupFragment and LoginFragment objects.
The anatomy of a Fragment transition
In Chapter 4, “Animating Activity & Fragment Transitions With XML”, you learned how to apply navigation component fragment animations by using the enterAnim, exitAnim, popEnterAnim and popExitAnim fields in nav_graph.xml. The flow is similar when you use the transition framework, except you apply the animation arguments on the actual Fragment instead of in the navigation graph XML file.
Note: Unfortunately, you can’t use the Transition framework for fragment animations if you’re using the Jetpack Navigation library — only if you’re manually managing the fragment back stack yourself. Working with Jetpack limits you to the
animresource files you learned about in Chapter 3, “XML Animations”, and Chapter 4, “Animating Activity & Fragment Transitions With XML”.
Fragment exposes four relevant methods for applying Transitions:
-
exitTransition: Governs the animation that runs when the user navigates away from this
Fragment. -
enterTransition: Governs the animation that runs when this
Fragmentdisplays for the first time. -
reenterTransition: Governs the animation that runs when this
Fragmentreappears after another fragment pops off the back stack. It’s similar topopEnterAnimfrom the Jetpack navigation library. -
returnTransition: Governs the animation that runs when hiding this
Fragmentafter it’s popped off the back stack. It’s similar topopExitAnimfrom the Jetpack navigation library.
You’ll get the chance to use each of these methods later in this chapter. Now that you know how to set transitions on a Fragment, it’s time to add your first transition!
Creating a fade transition
The first animation you’ll build with the transition framework is a simple fade between the AuthFragment and the SignupFragment. The user triggers this animation by tapping the I’m new to Cinematic button on the initial authorization screen.
To create this animation, you need to set the exitTransition in AuthFragment and the enterTransition in SignupFragment.
Start by opening AuthFragment.kt and overriding onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
You’ll encapsulate the transition logic in one place by adding all your transition code to onCreate.
Now that you have a method to contain your transition logic, it’s time to create the fade animation. Add the following below the call to super.onCreate in the onCreate you just added:
exitTransition = Fade()
Note: Make sure to import the
androidXversion of theFadetransition. The Transition framework, like many other classes in Android, has two versions: platform and AndroidX. The AndroidX version is regularly updated and much less buggy.
Here, you set the exitTransition property of the Fragment to the Fade transition. Fade is a simple transition that fades the alpha of the layout in or out depending on how the layout changes. When the layout goes from hidden to shown, Fade fades the layout in. Alternatively, if the visibility changes from shown to hidden, Fade fades the layout out.
Next, you need to add some similar code to SignupFragment.
Fading the signup screen in
Now that you’ve set the exitTransition on AuthFragment, it’s time to do the same song and dance in SignupFragment. Start by opening SignupFragment.kt and overriding onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Next, set enterTransition:
enterTransition = Fade()
Since you want SignupFragment to fade in, you set the enterTransition field.
Build and run, then tap the I’m new to Cinematic button. You’ll see the following animation:
Now, tap the back button. You’ll see the same animation, but in reverse: SignupFragment fades out while AuthFragment fades in.
That’s great! But why is it happening? You didn’t set the reenterTransition or returnTransition arguments, so why do you have an animation when you pop SignupFragment off the back stack?
A Fragment will default to using the same transition for the reenterTransition as for the exitTransition. It also defaults to using the same transition for the returnTransition as for the enterTransition. Since the Fade transition fades views in or out depending on the visibility change, the return animation looks the way you’d expect. Woohoo, free animations!
The fade animation looks fine, but it’d be great to add something just a bit snazzier so the app feels professional. Luckily, you can leverage transitions from the Material Design library for some beautiful and subtle animations!
Using Material Design transitions
Google produced the Material Design library for Android to help developers make Material Design-oriented apps. Open build.gradle and you’ll see that it already includes the Material Design library:
implementation 'com.google.android.material:material:1.3.0'
The Material Design library provides several beautiful, out-of-the-box transitions that you can use in your Fragment or Activity transitions. Among the snazziest is the MaterialSharedAxis transition. MaterialSharedAxis applies a subtle slide combined with a fade along any axis you want.
Next, you’ll use MaterialSharedAxis to replace the Fade you added earlier. Your goal is to have AuthFragment’s layout slide over and fade out to reveal SignupFragment’s layout.
Start by opening AuthFragment.kt and replacing the Fade transition with a MaterialSharedAxis transition:
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
Note: When importing the library, select
com.google.android.material.transition.MaterialSharedAxis.
MaterialSharedAxis takes two arguments:
- The axis along which the animation should slide. In this case, you want to slide the view out on the horizontal axis, so you’ve provided the X-axis here.
- A Boolean value indicating whether the transition should slide left or right along the X-axis.
Trueindicates the view should slide left, whilefalseindicates it should slide right. Here, you providetruebecause you want the layout to slide out to the left.
Next, open SignupFragment.kt and delete the line declaring the enterTransition. Since the goal is to have the AuthFragment view slide and fade out to reveal the SignupFragment view, you don’t need a transition for the SignupFragment — it will just appear as AuthFragment slides out.
Now, build and run the app and tap the I’m new to Cinematic button. You’ll see the following animation:
The animation looks good, but it’s a bit quick. Ideally, AuthFragment should take just a touch longer to animate out so the user sees the full effect of the animation. You can fix this by using setDuration, which is exposed on Transition, to update the animation’s duration.
Back in AuthFragment, add the apply block to the end of the line:
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true).apply {
duration = 1000
}
This sets the duration on the MaterialSharedAxis exit transition.
Now, build and run again. The animation is slower and feels more deliberate.
The animation looks solid so far, but you still need to do some cleanup work.
Cleaning up the material transition
Two major areas need improvement in the existing AuthFragment-to-SignupFragment animation:
- When you tap
SignupFragment’s back button, the screen flashes white instead of animating cleanly. - Sometimes, you see a strange artifact where the poster background in the
AuthFragmentflashes for a moment during the animation.
You’ll start by tackling the white flash when you tap the back button on SignupFragment. The reason for the flash is quite simple: You didn’t set a transition in SignupFragment, so when you tap the back button, its view immediately disappears. Then the AuthFragment’s view fades and slides in using the MaterialSharedAxis transition you defined earlier.
Note: Remember that a
Fragmentwill use theexitTransitionthat you set as thereenterTransitionif you didn’t set areenterTransitionmanually. That’s why you’re using theMaterialSharedAxistransition you defined as theexitTransitionfor thereenterTransition!
To fix the white flash, you’ll need to define a returnTransition on the SignupFragment.
Add the following in SignupFragment’s onCreate:
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false).apply {
duration = 1000
}
You’re again using a MaterialSharedAxis on the X-axis with a duration of 1,000 milliseconds. This time, you’re setting the forward field to false so the SignupFragment view animates to the right instead of the left when the user pops SignupFragment.
Build and run again. You’ll see an animation that looks like this:
Look at that! That is one terrible-looking animation. What gives?
It’s hard to pinpoint, but what’s happening here is that the SignupFragments view is sliding out to the right while the AuthFragments view is sliding in from the right. The result is a jarring animation that leaves a lot of empty space while the two animations are running. That’s not what you want — you want the AuthFragments view to slide in from the left rather than from the right.
AuthFragments view slides in from the right because it’s using the same transition for the reenter transition as you specified for the exitTransition. Unfortunately, MaterialSharedAxis doesn’t care whether the view is animating in or out when it comes to the direction of the slide — it’s entirely driven by the forward argument.
You’ll solve this problem by providing a reenterTransition in AuthFragment manually, then setting the forward argument to false. Add the following below the line setting the exitTransition in AuthFragment:
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
The above code adds the reenterTransition to the AuthFragment. Now, build and run again. You’ll see a much more pleasing and consistent animation. Next, you need to tackle how AuthFragments poster image flashes during the animation!
Fixing flashing views with transition groups
Before tackling the flashing view bug, you need to understand how a transition typically animates views within a ViewGroup. The transition framework will attempt to animate each view individually within a ViewGroup unless you tell the system that it should animate the ViewGroup as a singular view. You do that using the transitionGroup property on ViewGroup.
The cause of the flash is that when you navigate from AuthFragment to SignupFragment, the poster grid’s ImageView is animating separately from the View that provides the gradient overlay above the poster grid. At times, that causes the poster grid to appear as if it doesn’t have a gradient overlay because the view that provides it has already animated away.
To fix this issue, you need to set android:transitionGroup on the ViewGroup that contains the poster grid and the overlay.
Start by opening fragment_auth.xml and navigating to the FrameLayout that contains the poster grid ImageView and the gradient View.
Next, add the following line below the layout_height declaration in the FrameLayout tag:
android:transitionGroup="true"
Now, the FrameLayout acts as a single unit while it transitions. You won’t see the temporary flash of the poster grid without the gradient overlay anymore, since the two will always be in sync.
Run the app again and tap the I’m new to Cinematic button. You’ll see a pristine animation.
At this point, your animation looks great, but you know what would be even better? If you did something funky with Cinematic’s title!
Animating individual views
One of the coolest things about the transition framework is that you can target individual views to run different animations. For this app, you’ll add a special animation to the Cinematic TextView so it slides up in the AuthFragment exit transition and slides back down in the SignupFragment enter transition.
You want to add this slide while also preserving the existing MaterialSharedAxis transition. To do this, you need to use a new tool in the
transition framework toolkit: transition sets.
Combining transitions with transition sets
A TransitionSet is a special transition that acts as a container for multiple other transitions. You can think of it as the transition framework version of the AnimationSet utility you used in Chapter 4, “Animating Activity & Fragment Transitions With XML”. Here, you’ll use a TransitionSet to combine the MaterialSharedAxis transition with a new Slide transition to achieve the animation you want.
Start by opening AuthFragment.kt and creating a new TransitionSet below the call to super.onCreate():
val exitTransitionSet = TransitionSet().apply {
}
Don’t worry about the empty apply block; you’ll use it later to populate the exitTransitionSet.
Next, create a new variable for the MaterialSharedAxis transition that you’re currently using for the exitTransition, above the exitTransitionSet declaration:
val materialSlideOut = MaterialSharedAxis(MaterialSharedAxis.X, true).apply {
duration = 1000
}
You’ll add the materialSlideOut transition to your exitTransitionSet in just a moment. But before you do, create one last transition above the materialSlideOut declaration — the Slide transition that will slide your title TextView up:
val logoSlideUp = Slide(Gravity.TOP).apply {
duration = 700
}
Slide is a Transition that works exactly as you’d expect it to: It slides the view in whichever direction you specify in the constructor. In this case, you want the logo to slide up, so you pass in Gravity.TOP.
Now that you’ve specified your transitions, you can add them all to the exitTransitionSet by placing the following commands into the apply block:
addTransition(materialSlideOut)
addTransition(logoSlideUp)
TransitionSets can play the transitions either together or one after the other. In this app, you want the MaterialSharedAxis and Slide transitions to happen at the same time, so add the following in the apply block of exitTransitionSet:
ordering = TransitionSet.ORDERING_TOGETHER
Note: If you wanted the transitions to play sequentially, you’d use
TransitionSet.ORDERING_SEQUENTIALinstead.
Now that your exitTransitionSet is ready to go, replace the old exitTransition with the following code:
exitTransition = exitTransitionSet
This assigns your exitTransitionSet as the transition to run when the Fragment exits.
You’re 99% of the way towards achieving some next-level transitions. But you still have to make sure that the Slide transition only targets the logo’s TextView. To do that, you’ll use the excludeTarget and addTarget APIs.
Targeting a specific view in a transition
Now that you’ve defined your transitions, all that’s left is to make sure the Slide transition only runs for the logo TextView while the MaterialSharedAxis transition runs for everything else.
To achieve that goal, you’ll use two new methods on a Transition:
-
excludeTarget: Tells the transition not to run on the given
View. -
addTarget: Tells the transition to only run on the provided
View(s).
First, you want to make sure that the MaterialSharedAxis transition doesn’t run on the logo TextView. You want the logo TextView to slide up, not slide to the side and fade out.
Add the following call in the apply block of the materialSlideOut declaration:
excludeTarget(R.id.logo, true)
The excludeTarget call will ensure that the MaterialSharedAxis transition will not run on the logo TextView. Pretty easy, huh?
Next, you need to make sure that the Slide transition only runs on the logo TextView. Add the following in the apply block of the logoSlideUp transition:
addTarget(R.id.logo)
As mentioned earlier, the addTarget call will force the given Transition to only run on the targets that you’ve added. You can add as many targets as you want; you don’t have to stick to just one View. Calling addTarget modifies the behavior of the Transition pretty heavily — normally, a Transition will operate on all the Views in the Fragment. As soon as you call addTarget, you change the behavior so it now only operates on the Views you’ve added.
Build and run the app. You’ll see the Cinematic logo slide up while the rest of the layout slides to the left and fades out like normal. Nice!
Sliding the logo view back down
The only thing left to do to finish this beautiful animation is to have the logo TextView slide down when you get to the SignupFragment.
To do that, you’ll need to add a Slide enter transition, then use the addTarget API to target the logo TextView.
Open SignupFragment, then add the following below the call to super.onCreate():
enterTransition = Slide(Gravity.TOP).addTarget(R.id.signup_logo).setDuration(700)
Since the only enter transition will be the logo sliding down, you don’t need to use a TransitionSet. Instead, just create a new Slide transition that will slide down from the TOP, targeting the signup_logo TextView, then set the duration to 700 milliseconds.
Now, build and run and tap the sign-up button. The logo slides up on the AuthFragment screen, but the logo doesn’t slide down in the SignupFragment screen. What gives?
Well, you know those times when you’re building an Android app and you say to yourself, “Wow, this API is working exactly as I expected it to!”? This isn’t one of those times.
Recall that you can use the transitionGroup flag to specify that a ViewGroup should transition as one singular unit. There are some hidden… complexities there that you haven’t touched on yet. Specifically, that flag will be automatically set to true if you set a background for the ViewGroup.
Open fragment_signup.xml. If you look at the top-level ConstraintLayout in the file, you’ll see that it does, indeed, have a background. That means the transition framework is treating that ConstraintLayout as one single unit — and animating it as a single unit as well.
It also means that the transition framework will ignore any transitions that target a View within the ViewGroup, since that ViewGroup is animating as one cohesive item. Animating a subview would break that behavior.
To fix the issue and see the logo slide, you need to specify that the top-level ConstraintLayout should not operate as a single group. Add the following to the ConstraintLayouts declaration:
android:transitionGroup="false"
Now build and run. The logo will slide in and out as you expected:
There’s only one problem — the white flash is back when you back out of the SignupFragment! You’ll fix that next by modifying the logic that decides when the view should act as a transition group.
Changing the transition group logic
The screen flashes because, now that the layout isn’t a transition group, the top-level layout fades out at a different time than the other views on the screen. Because the top-level layout contains the background for the screen, you see the bare white screen exposed behind it.
To fix the issue, you need to toggle the transition group logic when the user taps the back button. You’ll do this by setting the transitionGroup flag programmatically when the user goes back.
In SignupFragment.kt, add the following below the block of code setting the click listener on the sign-up button in onCreateView:
// 1
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner) {
// 2
binding.root.isTransitionGroup = true
// 3
parentFragmentManager.popBackStack()
}
That’s a dense chunk of code. Here’s a breakdown of what’s happening:
- First, you register a callback with the
onBackPressedDispatcherto notify you when the user taps the back button. - Then, you toggle the
isTransitionGroupflag on the rootViewof the layout totrueso the animation will run as expected. - Finally, you pop the
Fragmentback stack to popSignupFragmentand showAuthFragmentagain.
Run the app again. Tap the sign-up button, then use the back button. The result will be a beautiful forward and backward animation.
Congratulations! You’ve finished designing an elegant, subtle and meaningful animation using the Transition framework.
Challenges
Challenge 1: Add transition animations to LoginFragment
Right now, the animations run beautifully when you navigate from AuthFragment to SignupFragment, but the LoginFragment doesn’t have any transitions. As a result, the animation looks off when you navigate from AuthFragment to LoginFragment. In this challenge, you’ll update the LoginFragment to show the same animations as you used for SignupFragment.
First, you need to override onCreate in LoginFragment, just as you did for AuthFragment and SignupFragment.
Then, you’ll set the enterTransition to a Slide that targets the logo.
Next, you’ll override the returnTransition to be a MaterialSharedAxis transition, as in SignupFragment.
You’ll also need to set the transitionGroup parameter to false in fragment_login.xml to make the Slide work.
Finally, to avoid the white flash when you back out of the LoginFragment, you’ll need to add a back-press listener once again, then disable the transitionGroup in the root layout when the user taps the back button.
Challenge 2: Add a fade transition to work with the logo slide transition
Now that the logo slides in on both the LoginFragment and SignupFragment, you can take it one step farther! In this challenge, you’ll have the logo slide in while also fading in.
First, you need to define a TransitionSet to contain both the Slide transition and a new Fade transition.
Then, you’ll pull out the Slide transition into its own variable. After that, you’ll need to add another variable representing a Fade transition. Both should target the logo TextView — either signup_logo or login_logo, depending on the class.
You’ll then add both those transitions to the TransitionSet and set the TransitionSet as the enter transition.
To make the fade animation a bit more noticeable, set the duration of the Fade to be slightly longer than the duration of the Slide. 1,000 milliseconds for the Slide and 2,000 milliseconds for the Fade works well!
You can find the solution to the challenges above in the challenges folder.
Key points
- The Transition framework is an alternative way to create beautiful
FragmentandActivityanimations. - Make sure to use the AndroidX version of the transition framework, rather than the platform version.
- You can use the
enterTransition/exitTransition/reenterTransition/returnTransitionproperties onFragmentto set transitions for differentFragmentscenarios. - Use the Material Design library and the
MaterialSharedAxistransition for some easy animation wins. -
TransitionSetlets you combine multiple transitions. - You can also use
FadeandSlideto easily fade and slide views into the scene. - Target individual views with the
addTargetAPI. - Remove individual views from a given transition by using the
remoteTargetAPI. - If you want a
ViewGroupto animate as one unit, set thetransitionGroupflag in XML or theisTransitionGroupproperty in code. - If you see strange behavior like white flashes or views not animating, there’s a good chance you need to tweak the
transitionGroupproperty.
Now get ready! In the next chapter you’ll be adding some motion to Cinematic using Element Transitions!