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!
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 like a 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 motion sick!
In part 1 I'm going to take you through creating shared element transitions for
Activity scenarios. In later posts we'll explore
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
Animatorusing 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
<!-- 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 contains a single button and an
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
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
ImageView can have different
transitionName. I find it easier to keep them the same but it's not necessary.
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
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
that we set on both our which has to match the target
Views in our XML layouts
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
Views in our layouts,
ViewCompat to get the
transitionName from the
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
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
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 shared element transitions.
Thanks to Wesley Ellis, Dimitris Karittevlis and Marcos Holgado for proof reading