Transitions in Material Design apps provide visual continuity. As the user navigates the app, views in the app change state. Motion and transformation reinforce the idea that interfaces are tangible, connecting common elements from one view to the next.
This post aims to provide guidelines and implementation for a specific continuous transition between Android Fragments. We will demonstrate how to implement a transition from an image in a RecyclerView into an image in a ViewPager and back, using 'Shared Elements' to determine which views participate in the transition and how. We will also handle the tricky case of transitioning back to the grid after paging to an item that was previously offscreen.
This is the result we are aiming for:
What are shared elements?
A shared element transition determines how views that are present in two fragments transition between them. For example, an image that is displayed on an ImageView on both Fragment A and Fragment B transitions from A to B when B becomes visible.
There are numerous previously published examples which explain how shared elements work and how to implement a basic Fragment transition. This post will skip most of the basics and will walk through the specifics on how to create a working transition into a ViewPager and back. However, if you'd like to learn more about transitions, I recommend starting by reading about transitions at the .
The challenges
Shared Element mapping
We would like to support a seamless back and forth transition. This includes a transition from the grid to the pager, and then a transition back to the relevant image, even when the user paged to a different image.
To do so, we will need to find a way to dynamically remap the shared elements in order to provide the Android's transition system what it needs to do its magic!
Delayed loading
Shared element transitions are powerful, but can be tricky when dealing with elements that need to be loaded before we can transition to them. The transition may simply not work as expected when views at the target fragment are not laid out and ready.
In this project, there are two areas where a loading time affects the shared element transition:
- It takes a few milliseconds for the
ViewPagerto load its internal fragments. Additionally, it takes time to load an image into the displayed pager fragment (may even include a download time for the asset).
- The
RecyclerViewalso faces a similar delay when loading the images into its views.
Demo app design
Basic structure
Before we dive into the juicy transitions, here is a little bit about how the demo app is structured.
, which loads images into views asynchronously. The images in the demo app are bundled with it. However, you may easily convert the
ImageData class to hold URL strings that point to online images.Coordinating a selected/displayed position
To communicate the selected image position between the fragments, we will use the MainActivity as a place to store the position.
When an item is clicked, or when a page is changed, the MainActivity is updated with the relevant item's position.
The stored position is later used in several places:
- When determining which page to show in the
ViewPager.
- When navigating back to the grid and auto-scrolling to the position to make sure it's visible.
- And of course, when hooking up the transitions callbacks, as we'll see in the next section.
Setting up the transitions
As mentioned above, we will need to find a way to dynamically remap the shared elements in order to give the transition system what it needs to do its magic.
Using a static mapping by setting up transitionName attributes for the image views at the XML will not work, as we are dealing with an arbitrary amount of views that share the same layout (e.g. views inflated by the RecyclerView adapter, or views inflated by the ImageFragment).
To accomplish this, we'll use some of what the transition system provides us:
- We set a transition name on the image views by calling
to interceptin our fragments'onCreateView(), and once the image is loaded, we start the transition by calling
As you may have noticed, the transition is still not completely polished with this setup. The fade animation is running for all the grid's card views, including the card that holds the image that transitions to the pager.
To fix it, we exclude the clicked card from the exit transition before commiting the fragment transaction at the GridAdapter.
// The 'view' is the card view that was clicked to initiate the transition.
((TransitionSet) fragment.getExitTransition()).excludeTarget(view, true);
After this change, the animation looks much better (the clicked card doesn't fade out as part of the exit transition, while the rest of the cards fade out):
SOCIAL SHARE CARD GENERATOR