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:
  1. It takes a few milliseconds for the ViewPager to 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).
  2. The RecyclerView also 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.
The MainActivity loads a GridFragment to present a RecyclerView of images. The RecyclerViewadapter loads the image items (a constant array that is defined at the ImageData class), and manages the onClick events by replacing the displayed GridFragment with an ImagePagerFragment.
The ImagePagerFragment adapter loads the nested ImageFragments to display the individual images when paging happens.
Note: The demo app implementation uses Glide, 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:
  1. We set a transition name on the image views by calling setTransitionName. This will identify the view with a unique name for the transition. setTransitionName is called when binding a view at the grid's RecyclerView adapter, and onCreateView at the ImageFragment. In both locations, we use the unique image resource as a name to identify the view.
  2. We set up SharedElementCallbacks to intercept onMapSharedElements and adjust the mapping of the shared element names to views. This will be done when exiting the GridFragmentand when entering the ImagePagerFragment.

Setting the FragmentManager transaction

The first thing we set up to initiate a transition for a fragment replacement is at the FragmentManager transaction preparation. We need to inform the system that we have a shared element transition.
fragment.getFragmentManager()
.beginTransaction()
.setReorderingAllowed(true) // setAllowOptimization before 26.1.0
.addSharedElement(imageView, imageView.getTransitionName())
.replace(R.id.fragment_container,
new ImagePagerFragment(),
ImagePagerFragment.class.getSimpleName())
.addToBackStack(null)
.commit();
The setReorderingAllowed is set to true. It will reorder the state changes of fragments to allow for better shared element transitions. Added fragments will have onCreate(Bundle) called before replaced fragments have onDestroy() called, allowing the shared view to get created and laid out before the transition starts.

Image transition

To define how the image transitions when it animates to its new location, we set up a TransitionSet in an XML file and load it at the ImagePagerFragment.
Transition transition =
TransitionInflater.from(getContext())
.inflateTransition(R.transition.image_shared_element_transition);
setSharedElementEnterTransition(transition);
xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="375"
android:interpolator="@android:interpolator/fast_out_slow_in"
android:transitionOrdering="together">