Shared Element Transitions are a great way of implementing Material motion and adding some flair to your app. Shared element transitions help the user on their journey through your app by giving them a focal point as they go from screen to screen. They can make your app feel whole, rather than a bunch of separate screens slapped together. They shouldn't be abused though, in some case too many elements darting all over the screen may make the user feel a bit sick!

In part 1 I'm going to take you through creating shared element transitions for Activity to Activity scenarios. In later posts we'll explore Fragment to Fragment, RecyclerView and setting up some combinations. If there are any other scenarios you would like to see then please tweet me or let me know in the comments.

Shared element transitions are only available on 21+. This won't affect older Android versions where everything reverts to a default transition when moving between screens. That is provided you've wrapped any logic appropriately so that the app doesn't crash at runtime (Android Studio should warn you about this).

How do they work?

Here's a simple example:

  • Activity A has an image in it. When you tap the image you launch Activity B.
  • Activity B loads everything transparently on screen.
  • The framework does some calculations, finding out where the image is starting and where it's going to end, taking into consideration how it's being asked to do it (the type of transition).
  • It creates an Animator using the differences which handles moving everything for you.
  • The framework finally tells Activity A to hide it's shared element, runs the animator with Activity B's shared element animating into it's final position.

A more detailed explanation can be found here in this great article by Alex Lockwood.

Shared element transitions take place in the window's ViewOverlay. This overlay layer sits on top of all other views, ensuring that any shared element set doesn't get drawn over ruining the Shared Element Transition experience.

A great example is in Google Play Music. When selecting an album from a list you are transitioned to a detail screen listing the album's songs. The album cover is a shared element, transitioning from a small square to a larger one at the top of the screen.

Google Play Music Shared Element Transition Example

We're going to create something similar but simpler. With animals. Everyone loves animals.

Activity to Activity

First of all we need to enable windowContentTransitions. If you are targeting <21 then you'll need to use your styles.xml within a values-v21 folder. Add this line to your base application theme in the appropriate styles file:

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        //add this line to your file
    <item name="android:windowContentTransitions">true</item>
</style>

Shared element transitions from activities are simple to implement. Let's assume you have two activities, ActivityA and ActivityB. ActivityA contains a single button and an ImageView. ActivityB contains a larger ImageView at the top and some detail text underneath. Something like this:

Screenshots of Activity A and B

There are two ways of defining shared element transitions. You can do them in your XML layout using the transitionName attribute or programatically by calling setTransitionName() on a View. We're going to be using the former as it's easier but a future blog post will detail setting them dynamically.

First let's set the transitionName on BOTH our ImageView in our XML layouts. This is so that the framework knows where we want our transitioning ImageView to go. It's pretty simple, just add the attribute like so:

<ImageView
    android:id="@+id/simple_activity_a_imageView"
    android:layout_width="128dp"
    android:layout_height="96dp"
    android:layout_alignParentBottom="true"
    android:layout_centerHorizontal="true"
    android:layout_marginBottom="80dp"
    android:scaleType="centerCrop"
    android:src="@drawable/lion"
    android:transitionName="simple_activity_transition"
    tools:ignore="UnusedAttribute" />

Note: I'm entering the String for clarity in this post but I advise that you actually place this in your strings.xml, like I do in the source code. You may get some lint warnings if you target <21 so you can either ignore them as is done above with tools:ignore="UnusedAttribute".

The transitionName has to be unique on the screen to any other element transitions you have. Otherwise you’ll just confuse the framework and may get some weird results. in the view hierarchy. That means that ActivityA and ActivityB ImageView can have different transitionName. I find it easier to keep them the same but it's not necessary.

In ActivityA in the OnClickListener for our button we want the following:

Intent intent = new Intent(SimpleActivityA.this, SimpleActivityB.class);
ActivityOptionsCompat options = ActivityOptionsCompat.
                       makeSceneTransitionAnimation(SimpleActivityA.this,
                            imageView,
                            ViewCompat.getTransitionName(imageView));
startActivity(intent, options.toBundle());

On line 1 we're just creating an Intent that we're going to use to launch ActivityB. In line 2 were creating a options file with ActivityOptionsCompat from the support library. If you're supporting 21+ you can just use ActivityOptions. The makeSceneTransitionAnimation() creates our animation. We just have to pass it 3 things. The first is simply a Context. Secondly we need the sharedElementthat is transitioning which is of type View. Finally the sharedElementName which is also the transitionName that we set on both our Views in our XML layouts which has to match the target View transitionName. Note the last parameter should not be null even though it's possible to pass it in with no lint warnings 😕

We give it SimpleActivityA.this for our context (we're in the OnClickListener for the button remember). Next we pass in our ImageView that is transitioning. Finally we pass in our transitionName we set on the Views in our layouts, "simple_activity_transition". Finally, we use ViewCompat to get the transitionName from the ImageView. If ActivityB ImageView had a different transitionName then this it where you'd specify it.

In the last line we start our activity and add our options alongside it as a Bundle.

ActivityB needs little setup. As long as you have added the transitionName to the layout for it as well as set the SAME on the ImageView and it's the same one specified when creating the ActivityOptionsCompat in ActivityB, it should now just work 😃

The source code for Part 1 can be found here

In Part 2 next week we'll explore doing Fragment to Fragment shared element transitions.

Update (5/03/17): I've made some minor adjustments to this post since first releasing. There's nothing better than having 1000's of eyes on your work for reviewing!

Thanks to Wesley Ellis, Dimitris Karittevlis and Marcos Holgado for proof reading