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.
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:
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 sharedElement
that is transitioning which is of type View
. Finally the sharedElementName
which is also the transitionName
that we set on both our which has to match the target View
s in our XML layoutsView
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 . Finally, we use transitionName
we set on the View
s in our layouts, "simple_activity_transition"
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 and it's the same one specified when creating the ImageView
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