Content Transitions In-Depth (part 2)
This post will give an in-depth analysis of content transitions and their role in the Activity and Fragment Transitions API. This is the second 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 summarizing what we learned about content transitions in part 1 and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop.
What is a Content Transition?
A content transition determines how the non-shared views—called transitioning views—enter or exit the scene during an Activity or Fragment transition. Motivated by Google’s new Material Design language, content transitions allow us to coordinate the entrance and exit of each Activity/Fragment’s views, making the act of switching between screens smooth and effortless. Beginning with Android Lollipop, content transitions can be set programatically by calling the following Window
and Fragment
methods:
setExitTransition()
-A
’s exit transition animates transitioning views out of the scene whenA
startsB
.setEnterTransition()
-B
’s enter transition animates transitioning views into the scene whenA
startsB
.setReturnTransition()
-B
’s return transition animates transitioning views out of the scene whenB
returns toA
.setReenterTransition()
-A
’s reenter transition animates transitioning views into the scene whenB
returns toA
.
As an example, Video 2.1 illustrates how content transitions are used in the Google Play Games app to achieve smooth animations between activities. When the second activity starts, its enter content transition gently shuffles the user avatar views into the scene from the bottom edge of the screen. When the back button is pressed, the second activity’s return content transition splits the view hierarchy into two and animates each half off the top and bottom of the screen.
So far our analysis of content transitions has only scratched the surface; several important questions still remain. How are content transitions triggered under-the-hood? Which types of Transition
objects can be used? How does the framework determine the set of transitioning views? Can a ViewGroup
and its children be animated together as a single entity during a content transition? In the next couple sections, we’ll tackle these questions one-by-one.
Content Transitions Under-The-Hood
Recall from the previous post that a Transition
has two main responsibilities: capturing the start and end state of its target views and creating an Animator
that will animate the views between the two states. Content transitions are no different: before a content transition’s animation can be created, the framework must give it the state information it needs by altering each transitioning view’s visibility. More specifically, when Activity A
starts Activity B
the following sequence of events occurs:1
- Activity
A
callsstartActivity()
.- The framework traverses
A
's view hierarchy and determines the set of transitioning views that will exit the scene whenA
's exit transition is run. A
's exit transition captures the start state for the transitioning views inA
.- The framework sets all transitioning views in
A
toINVISIBLE
. - On the next display frame,
A
's exit transition captures the end state for the transitioning views inA
. A
's exit transition compares the start and end state of each transitioning view and creates anAnimator
based on the differences. TheAnimator
is run and the transitioning views exit the scene.
- The framework traverses
- Activity
B
is started.- The framework traverses
B
's view hierarchy and determines the set of transitioning views that will enter the scene whenB
's enter transition is run. The transitioning views are initially set toINVISIBLE
. B
's enter transition captures the start state for the transitioning views inB
.- The framework sets all transitioning views in
B
toVISIBLE
. - On the next display frame,
B
's enter transition captures the end state for the transitioning views inB
. B
's enter transition compares the start and end state of each transitioning view and creates anAnimator
based on the differences. TheAnimator
is run and the transitioning views enter the scene.
- The framework traverses
By toggling each transitioning view’s visibility between INVISIBLE
and VISIBLE
, the framework ensures that the content transition is given the state information it needs to create the desired animation. Clearly all content Transition
objects then must at the very least be able to capture and record each transitioning view’s visibility in both its start and end states. Fortunately, the abstract Visibility
class already does this work for you: subclasses of Visibility
need only implement the onAppear()
and onDisappear()
factory methods, in which they must create and return an Animator
that will either animate the views into or out of the scene. As of API 21, three concrete Visibility
implementations exist—Fade
, Slide
, and Explode
—all of which can be used to create Activity and Fragment content transitions. If necessary, custom Visibility
classes may be implemented as well; doing so will be covered in a future blog post.
Transitioning Views & Transition Groups
Up until now, we have assumed that content transitions operate on a set of non-shared views called transitioning views. In this section, we will discuss how the framework determines this set of views and how it can be further customized using transition groups.
Before the transition starts, the framework constructs the set of transitioning views by performing a recursive search on the Activity window’s (or Fragment’s) entire view hierarchy. The search begins by calling the overridden recursive ViewGroup#captureTransitioningViews
method on the hierarchy’s root view, the source code of which is given below:
/** @hide */
@Override
public void captureTransitioningViews(List<View> transitioningViews) {
if (getVisibility() != View.VISIBLE) {
return;
}
if (isTransitionGroup()) {
transitioningViews.add(this);
} else {
int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
child.captureTransitioningViews(transitioningViews);
}
}
}
The recursion is relatively straightforward: the framework traverses each level of the tree until it either finds a VISIBLE
leaf view or a transition group. Transition groups essentially allow us to animate entire ViewGroup
s as single entities during an Activity/Fragment transition. If a ViewGroup
’s isTransitionGroup()
2 method returns true
, then it and all of its children views will be animated together as one. Otherwise, the recursion will continue and the ViewGroup
’s transitioning children views will be treated independently during the animation. The final result of the search is the complete set of transitioning views that will be animated by the content transition.3
An example illustrating transition groups in action can be seen in Video 2.1 above. During the enter transition, the user avatars shuffle into the screen independently of the others, whereas during the return transition the parent ViewGroup
containing the user avatars is animated as one. The Google Play Games app likely uses a transition group to achieve this effect, making it look as if the current scene splits in half when the user returns to the previous activity.
Sometimes transition groups must also be used to fix mysterious bugs in your Activity/Fragment transitions. For example, consider the sample application in Video 2.2: the calling Activity displays a grid of Radiohead album covers and the called Activity shows a background header image, the shared element album cover, and a WebView
. The app uses a return transition similar to the Google Play Games app, sliding the top background image and bottom WebView
off the top and bottom of the screen respectively. However, as you can see in the video, a glitch occurs and the WebView
fails to slide smoothly off the screen.
So what went wrong? Well, the problem stems from the fact that WebView
is a ViewGroup
and as a result is not selected to be a transitioning view by default. Thus, when the return content transition is run, the WebView
will be ignored entirely and will remain drawn on the screen before being abruptly removed when the transition ends. Fortunately, we can easily fix this by calling webView.setTransitionGroup(true)
at some point before the return transition begins.
Conclusion
Overall, this post presented three important points:
- A content transition determines how an Activity or Fragment’s non-shared views—called transitioning views—enter or exit the scene during an Activity or Fragment transition.
- Content transitions are triggered by changes made to its transitioning views’ visibility and should almost always extend the abstract
Visibility
class as a result. - Transition groups enable us to animate entire
ViewGroup
s as single entities during a content transition.
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 A similar sequence of events occurs during return/reenter transitions for both Activities and Fragments. ↩
2 Note that isTransitionGroup()
will return true
if the ViewGroup
has a non-null
background drawable and/or non-null
transition name by default (as stated in the method’s documentation). ↩
3 Note that any views that were explicitly added or excluded in the content Transition
object will also be taken into account when the transition is run. ↩