Getting Started with Activity & Fragment Transitions (part 1)
This post gives a brief overview of Transition
s and introduces the new Activity & Fragment transition APIs that were added in Android 5.0 Lollipop. This is the first of a series of posts I will be writing on the topic:
- Part 1: Getting Started with Activity & Fragment Transitions
- Part 2: Content Transitions In-Depth
- Part 3a: Shared Element Transitions In-Depth
- Part 3b: Postponed Shared Element Transitions
- Part 3c: Implementing Shared Element Callbacks (coming soon!)
- Part 4: Activity & Fragment Transition Examples (coming soon!)
Until I write part 4, an example application demonstrating some advanced activity transitions is available here.
We begin by answering the following question: what is a Transition
?
What is a Transition
?
Activity and Fragment transitions in Lollipop are built on top of a relatively new feature in Android called Transition
s. Introduced in KitKat, the transition framework provides a convenient API for animating between different UI states in an application. The framework is built around two key concepts: scenes and transitions. A scene defines a given state of an application’s UI, whereas a transition defines the animated change between two scenes.
When a scene changes, a Transition
has two main responsibilities:
- Capture the state of each view in both the start and end scenes, and
- Create an
Animator
based on the differences that will animate the views from one scene to the other.
As an example, consider an Activity
which fades its views in or out when the user taps the screen. We can achieve this effect with only a few lines using Android’s transition framework, as shown in the code1 below:
public class ExampleActivity extends Activity implements View.OnClickListener {
private ViewGroup mRootView;
private View mRedBox, mGreenBox, mBlueBox, mBlackBox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootView = (ViewGroup) findViewById(R.id.layout_root_view);
mRootView.setOnClickListener(this);
mRedBox = findViewById(R.id.red_box);
mGreenBox = findViewById(R.id.green_box);
mBlueBox = findViewById(R.id.blue_box);
mBlackBox = findViewById(R.id.black_box);
}
@Override
public void onClick(View v) {
TransitionManager.beginDelayedTransition(mRootView, new Fade());
toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);
}
private static void toggleVisibility(View... views) {
for (View view : views) {
boolean isVisible = view.getVisibility() == View.VISIBLE;
view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);
}
}
}
To better understand what happens under-the-hood in this example, let’s analyze the process step-by-step assuming that each view is initially VISIBLE
on screen:
- A click is detected and the developer calls
beginDelayedTransition()
, passing the scene root and aFade
transition as the arguments. The framework immediately calls the transition’scaptureStartValues()
method for each view in the scene and the transition records each view’s visibility. - When the call returns, the developer sets each view in the scene to
INVISIBLE
. - On the next display frame, the framework calls the transition’s
captureEndValues()
method for each view in the scene and the transition records each view’s (recently updated) visibility. - The framework calls the transition’s
createAnimator()
method. The transition analyzes the start and end values of each view and notices a difference: the views areVISIBLE
in the start scene butINVISIBLE
in the end scene. TheFade
transition uses this information to create and return anAnimatorSet
that will fade each view’salpha
property to0f
. - The framework runs the returned
Animator
, causing all views to gradually fade out of the screen.
This simple example highlights two main advantages that the transition framework has to offer. First, Transition
s abstract the idea of Animator
s from the developer. As a result, Transition
s can significantly reduce the amount of code you write in your activities and fragments: all the developer must do is set the views’ start and end values and the Transition
will automatically construct an animation based on the differences. Second, animations between scenes can be easily changed by using different Transition
objects. Video 1.1, for example, illustrates the dramatically different effects we can achieve by replacing the Fade
transition with a Slide
or Explode
. As we will see moving forward, these advantages will allow us to build complex Activity and Fragment transition animations with a relatively small amount of code. In the next few sections, we will see for ourselves how this can be done using Lollipop’s new Activity and Fragment transition APIs.
Activity & Fragment Transitions in Android Lollipop
As of Android 5.0, Transition
s can now be used to perform elaborate animations when switching between different Activity
s or Fragment
s. Although Activity and Fragment animations could already be specified in previous platform versions using the Activity#overridePendingTransition()
and FragmentTransaction#setCustomAnimation()
methods, they were limited in that they could only animate the entire Activity/Fragment container as a whole. The new Lollipop APIs take this a step further, making it possible to animate individual views as they enter or exit their containers and even allowing us to animate shared views from one Activity/Fragment container to the other.
Let’s begin by discussing the terminology that will be used in this series of posts. Note that although the terminology below is defined in terms of Activity transitions, the exact same terminology will be used for Fragment transitions as well:
Let
A
andB
be activities and assume activityA
starts activityB
. We refer toA
as the “calling Activity” (the activity that “calls”startActivity()
) andB
as the “called Activity”.
The Activity transition APIs are built around the idea of exit, enter, return, and reenter transitions. In the context of activities A
and B
defined above, we can describe each as follows:
Activity
A
’s exit transition determines how views inA
are animated whenA
startsB
.Activity
B
’s enter transition determines how views inB
are animated whenA
startsB
.Activity
B
’s return transition determines how views inB
are animated whenB
returns toA
.Activity
A
’s reenter transition determines how views inA
are animated whenB
returns toA
.
Lastly, the framework provides APIs for two types of Activity transitions—content transitions and shared element transitions—each of which allow us to customize the animations between Activities in unique ways:
A content transition determines how an activity’s non-shared views (also called transitioning views) enter or exit the activity scene.
A shared element transition determines how an activity’s shared elements (also called hero views) are animated between two activities.
Video 1.2 gives a nice illustration of content transitions and shared element transitions used in the Google Play Newsstand app. Although we can’t be sure without looking at the Newsstand source code, my best guess is that the following transitions are used:
- The exit and reenter content transitions for activity
A
(the calling activity) are bothnull
. We can tell because the non-shared views inA
are not animated when the user exits and reenters the activity.2 - The enter content transition for activity
B
(the called activity) uses a custom slide-in transition that shuffles the list items into place from the bottom of the screen. - The return content transition for activity
B
is aTransitionSet
that plays two child transitions in parallel: aSlide(Gravity.TOP)
transition targeting the views in the top half of the activity and aSlide(Gravity.BOTTOM)
transition targeting the views in the bottom half of the activity. The result is that the activity appears to “break in half” when the user clicks the back button and returns to activityA
. - The enter and return shared element transitions both use a
ChangeImageTransform
, causing theImageView
to be animated seamlessly between the two activities.
You’ve probably also noticed the cool circular reveal animation that plays under the shared element during the transition. We will cover how this can be done in a future blog post. For now, let’s keep things simple and familiarize ourselves with the Activity and Fragment transition APIs.
Introducing the Activity Transition API
Creating a basic Activity transition is relatively easy using the new Lollipop APIs. Summarized below are the steps you must take in order to implement one in your application. In the posts that follow, we will go through much more advanced use-cases and examples, but for now the next two sections will serve as a good introduction:
- Enable the new transition APIs by requesting the
Window.FEATURE_ACTIVITY_TRANSITIONS
window feature in your called and calling Activities, either programatically or in your theme’s XML.3 Material-themed applications have this flag enabled by default. - Set exit and enter content transitions for your calling and called activities respectively. Material-themed applications have their exit and enter content transitions set to
null
andFade
respectively by default. If the reenter or return transitions are not explicitly set, the activity’s exit and enter content transitions respectively will be used in their place instead. - Set exit and enter shared element transitions for your calling and called activities respectively. Material-themed applications have their shared element exit and enter transitions set to
@android:transition/move
by default. If the reenter or return transitions are not explicitly set, the activity’s exit and enter shared element transitions respectively will be used in their place instead. -
To start an Activity transition with content transitions and shared elements, call the
startActivity(Context, Bundle)
method and pass the followingBundle
as the second argument:ActivityOptions.makeSceneTransitionAnimation(activity, pairs).toBundle();
where
pairs
is an array ofPair<View, String>
objects listing the shared element views and names that you’d like to share between activities.4 Don’t forget to give your shared elements unique transition names, either programatically or in XML. Otherwise, the transition will not work properly! - To programatically trigger a return transition, call
finishAfterTransition()
instead offinish()
. - By default, material-themed applications have their enter/return content transitions started a tiny bit before their exit/reenter content transitions complete, creating a small overlap that makes the overall effect more seamless and dramatic. If you wish to explicitly disable this behavior, you can do so by calling the
setWindowAllowEnterTransitionOverlap()
andsetWindowAllowReturnTransitionOverlap()
methods or by setting the corresponding attributes in your theme’s XML.
Introducing the Fragment Transition API
If you are working with Fragment transitions, the API is similar with a few small differences:
- Content exit, enter, reenter, and return transitions should be set by calling the corresponding methods in the
Fragment
class or as attributes in your Fragment’s XML tag. - Shared element enter and return transitions should be set by calling the corresponding methods in the
Fragment
class or as attributes in your Fragment’s XML. - Whereas Activity transitions are triggered by explicit calls to
startActivity()
andfinishAfterTransition()
, Fragment transitions are triggered automatically when a fragment is added, removed, attached, detached, shown, or hidden by aFragmentTransaction
. - Shared elements should be specified as part of the
FragmentTransaction
by calling theaddSharedElement(View, String)
method before the transaction is committed.
Conclusion
In this post, we have only given a brief introduction to the new Activitiy and Fragment transition APIs. However, as we will see in the next few posts having a solid understanding of the basics will significantly speed up the development process in the long-run, especially when it comes to writing custom Transition
s. In the posts that follow, we will cover content transitions and shared element transitions in even more depth and will obtain an even greater understanding of how Activity and Fragment transitions work under-the-hood.
As always, thanks for reading! Feel free to leave a comment if you have any questions, and don’t forget to +1 and/or share this blog post if you found it helpful!
1 If you want to try the example out yourself, the XML layout code can be found here. ↩
2 It might look like the views in A
are fading in/out of the screen at first, but what you are really seeing is activity B
fading in/out of the screen on top of activity A
. The views in activity A
are not actually animating during this time. You can adjust the duration of the background fade by calling setTransitionBackgroundFadeDuration()
on the called activity’s Window
. ↩
3 For an explanation describing the differences between the FEATURE_ACTIVITY_TRANSITIONS
and FEATURE_CONTENT_TRANSITIONS
window feature flags, see this StackOverflow post. ↩
4 To start an Activity transition with content transitions but no shared elements, you can create the Bundle
by calling ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()
. To disable content transitions and shared element transitions entirely, don’t create a Bundle
object at all—just pass null
instead. ↩