Content Transitions In-Depth (part 2)

Posted

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:

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 when A starts B.
  • setEnterTransition() - B’s enter transition animates transitioning views into the scene when A starts B.
  • setReturnTransition() - B’s return transition animates transitioning views out of the scene when B returns to A.
  • setReenterTransition() - A’s reenter transition animates transitioning views into the scene when B returns to A.

Video 2.1 - Content transitions in the Google Play Games app (as of v2.2). Click to play.

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

  1. Activity A calls startActivity().
    1. The framework traverses A's view hierarchy and determines the set of transitioning views that will exit the scene when A's exit transition is run.
    2. A's exit transition captures the start state for the transitioning views in A.
    3. The framework sets all transitioning views in A to INVISIBLE.
    4. On the next display frame, A's exit transition captures the end state for the transitioning views in A.
    5. A's exit transition compares the start and end state of each transitioning view and creates an Animator based on the differences. The Animator is run and the transitioning views exit the scene.
  2. Activity B is started.
    1. The framework traverses B's view hierarchy and determines the set of transitioning views that will enter the scene when B's enter transition is run. The transitioning views are initially set to INVISIBLE.
    2. B's enter transition captures the start state for the transitioning views in B.
    3. The framework sets all transitioning views in B to VISIBLE.
    4. On the next display frame, B's enter transition captures the end state for the transitioning views in B.
    5. B's enter transition compares the start and end state of each transitioning view and creates an Animator based on the differences. The Animator is run and the transitioning views enter the scene.

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);
        }
    }
}

Video 2.2 - A simple Radiohead app that illustrates a potential bug involving transition groups and WebViews. Click to play.

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 ViewGroups 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:

  1. 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.
  2. Content transitions are triggered by changes made to its transitioning views’ visibility and should almost always extend the abstract Visibility class as a result.
  3. Transition groups enable us to animate entire ViewGroups 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.

+1 this blog!

Android Design Patterns is a website for developers who wish to better understand the Android application framework. The tutorials here emphasize proper code design and project maintainability.

Find a typo?

Submit a pull request! The code powering this site is open-source and available on GitHub. Corrections are appreciated and encouraged! Click here for instructions.

Apps by me

Shape Shifter simplifies the creation of AnimatedVectorDrawable path morphing animations. View on GitHub.
2048++ is hands down the cleanest, sleekest, most responsive 2048 app for Android!