Shared Element Transitions In-Depth (part 3a)

Posted

This post will give an in-depth analysis of shared element transitions and their role in the Activity and Fragment Transitions API. This is the third 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.

Part 3 of this series will be broken up into three parts: part 3a will focus on how shared elements operate under-the-hood and part 3b and part 3c will focus more on the implementation-specific details of the API, such as the importance of postponing certain shared element transitions and implementing SharedElementCallbacks.

We begin by summarizing what we learned about shared element transitions in part 1 and illustrating how they can be used to achieve smooth, seamless animations in Android Lollipop.

What is a Shared Element Transition?

A shared element transition determines how shared element views—also called hero views—are animated from one Activity/Fragment to another during a scene transition. Shared elements are animated by the called Activity/Fragment’s enter and return shared element transitions,1 each of which can be specified using the following Window and Fragment methods:

  • setSharedElementEnterTransition() - B’s enter shared element transition animates shared element views from their starting positions in A to their final positions in B.
  • setSharedElementReturnTransition() - B’s return shared element transition animates shared element views from their starting positions in B to their final positions in A.

Video 3.1 - Shared element transitions in action in the Google Play Music app (as of v5.6). Click to play.

Video 3.1 illustrates how shared element transitions are used in the Google Play Music app. The transition consists of two shared elements: an ImageView and its parent CardView. During the transition, the ImageView seamlessly animates between the two activities while the CardView gradually expands/contracts into place.

Whereas part 1 only briefly introduced the subject, this blog post aims to give a much more in-depth analysis of shared element transitions. How are shared element transitions triggered under-the-hood? Which types of Transition objects can be used? How and where are shared element views drawn during the transition? In the next couple sections, we’ll tackle these questions one-by-one.

Shared Element Transitions Under-The-Hood

Recall from the previous two posts 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. Shared element transitions operate no differently: before a shared element transition can create its animation, it must first capture each shared element’s start and end state—namely its position, size, and appearance in both the calling and called Activities/Fragments. With this information, the transition can determine how each shared element view should animate into place.

Similar to how content transitions operate under-the-hood, the framework feeds the shared element transition this state information by directly modifying each shared element’s view properties at runtime. More specifically, when Activity A starts Activity B the following sequence of events occurs:2

  1. Activity A calls startActivity() and Activity B is created, measured, and laid out with an initially translucent window and transparent window background color.
  2. The framework repositions each shared element view in B to match its exact size and location in A. Shortly after, B’s enter transition captures the start state of all the shared elements in B.
  3. The framework repositions each shared element view in B to match its final size and location in B. Shortly after, B’s enter transition captures the end state of all the shared elements in B.
  4. B’s enter transition compares the start and end state of its shared element views and creates an Animator based on the differences.
  5. The framework instructs A to hide its shared element views from sight and the resulting Animator is run. As B’s shared element views animate into place, B’s window background gradually fades in on top A until B is entirely opaque and the transition completes.

Whereas content transitions are governed by changes to each transitioning view’s visibility, shared element transitions are governed by changes to each shared element view’s position, size, and appearance. As of API 21, the framework provides several different Transition implementations that can be used to customize how shared elements are animated during a scene change:

  • ChangeBounds - Captures the layout bounds of shared element views and animates the differences. ChangeBounds is frequently used in shared element transitions, as most shared elements will differ in size and/or location within either of the two Activities/Fragments.
  • ChangeTransform - Captures the scale and rotation of shared element views and animates the differences.3
  • ChangeClipBounds - Captures the clip bounds of shared element views and animates the differences.
  • ChangeImageTransform - Captures the transform matrices of shared element ImageViews and animates the differences. In combination with ChangeBounds, this transition allows ImageViews that change in size, shape, and/or ImageView.ScaleType to animate smoothly and efficiently.
  • @android:transition/move - A TransitionSet that plays all four transition types above in parallel. As discussed in part 1, if an enter/return shared element transition is not explicitly specified, the framework will run this transition by default.

In the example above, we also can see that shared element view instances are not actually “shared” across Activities/Fragments. In fact, almost everything the user sees during both enter and return shared element transitions is drawn directly inside B’s content view. Instead of somehow transferring the shared element view instance from A to B, the framework uses a different means of achieving the same visual effect. When A starts B, the framework collects all of the relevant state information about the shared elements in A and passes it to B. B then uses this information to initialize the start state of its shared elements views, each of which will initially match the exact position, size, and appearance they had in A. When the transition begins, everything in B except the shared elements are initially invisible to the user. As the transition progresses, however, the framework gradually fades in B’s Activity window until the shared elements in B finish animating and B’s window background is opaque.

Using the Shared Element Overlay4

Video 3.2 - A simple app that illustrates a potential bug that can result when the shared element overlay is disabled. Click to play.

Finally, before we can gain a complete understanding of how shared element transitions are drawn by the framework, we must discuss the shared element overlay. Although not immediately obvious, shared elements are drawn on top of the entire view hierarchy in the window’s ViewOverlay by default. In case you haven’t heard of it before, the ViewOverlay class was introduced in API 18 as a way to easily draw on top of a View. Drawables and views that are added to a view’s ViewOverlay are guaranteed to be drawn on top of everything else—even a ViewGroup’s children. With this in mind, it makes sense why the framework would choose to draw shared elements in the window’s ViewOverlay on top of everything else in the view hierarchy by default. Shared elements views should be the focus throughout the entire transition; the possibility of transitioning views accidentally drawing on top of the shared elements would immediately ruin the effect.5

Although shared elements are drawn in the shared element ViewOverlay by default, the framework does give us the ability to disable the overlay by calling the Window#setSharedElementsUseOverlay(false) method, just in case you ever find it necessary. If you ever do choose to disable the overlay, be wary of the undesired side-effects it might cause. As an example, Video 3.2 runs a simple shared element transition twice, with and without the shared element overlay enabled respectively. The first time the transition is run, the shared element ImageView animates as expected in the shared element overlay, on top of all other views in the hierarchy. The second time the transition is run, however, we can clearly see that disabling the overlay has introduced a problem. As the bottom transitioning view slides up into the called Activity’s content view, the shared element ImageView is partially covered as and is drawn below the transitioning view for nearly the first half of the transition. Although there is a chance that this could be fixed by altering the order in which views are drawn in the layout and/or by setting setClipChildren(false) on the shared element’s parent, these sort of “hacky” modifications can easily become unmanagable and more trouble than they are worth. In short, try not to disable the shared element overlay unless you find it absolutely necessary, and you’ll likely benefit from simpler and more dramatic shared element transitions as a result.

Conclusion

Overall, this post presented three important points:

  1. A shared element transition determines how shared element views—also called hero views—are animated from one Activity/Fragment to another during a scene transition.
  2. Shared element transitions are governed by changes to each shared element view’s position, size, and appearance.
  3. Shared elements are drawn on top of the entire view hierarchy in the window’s ViewOverlay by default.

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 Note that the Activity Transition API gives you the ability to also specify exit and reenter shared element transitions using the setSharedElementExitTransition() and setSharedElementReenterTransition() methods, although doing so is usually not necessary. For an example illustrating one possible use case, check out this blog post. For an explanation why exit and reenter shared element transitions are not available for Fragment Transitions, see George Mount’s answer and comments in this StackOverflow post.

2 A similar sequence of events occurs during the exit/return/reenter transitions for both Activities and Fragments.

3 One other subtle feature of ChangeTransform is that it can detect and handle changes made to a shared element view’s parent during a transition. This comes in handy when, for example, the shared element’s parent has an opaque background and is by default selected to be a transitioning view during the scene change. In this case, the ChangeTransform will detect that the shared element’s parent is being actively modified by the content transition, pull out the shared element from its parent, and animate the shared element separately. See George Mount’s StackOverflow answer for more information.

4 Note that this section only pertains to Activity Transitions. Unlike Activity Transitions, shared elements are not drawn in a ViewOverlay by default during Fragment Transitions. That said, you can achieve a similar effect by applying a ChangeTransform transition, which will have the shared element drawn on top of the hierarchy in a ViewOverlay if it detects that its parent has changed. See this StackOverflow post for more information.

5 Note that one negative side-effect of having shared elements drawn on top of the entire view hierarchy is that this means it will become possible for shared elements to draw on top of the System UI (such as the status bar, navigation bar, and action bar). For more information on how you can prevent this from happening, see this Google+ post.

+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!