Android Shared-Element Transitions for all

We all want our apps to have its personality beyond material and, activity transitions are a great way to make our users remember our app. Out of them, shared element transitions are probably the most commonly used of all.

Shared element transitions are fairly easy to implement in Lollipop+ but, if you target older Android versions, its kind of a big drawback as you will need to start checking for Android versions and implement fallback transitions for older devices…OR…you can be brave, implement “manually” the transitions yourself and be compatible with all devices.
Scary right? well, it turns out, is not that complicated and I will show you how.

Shared element transitions step-by-step

Let’s say you want to transition from Activity AActivity B and that they both share a element (e.g. a view). In that scenario, one of the best user experiences would be to transition to Activity B translating and resizing the shared element to its final location and size. That keeps the user focused on what is important and gives some sense of continuity and flow to your app. 
How would you achieve such transition? The following would be the high level steps.

  1. Activity A captures shared element start values and pass them to Activity B via an Intent
  2. Activity B starts completely transparent
  3. Activity B reads the bundled values and prepares the scene
  4. Activity B runs the shared element animation

I will go through those steps in some detail along the post and will show some example code. 
But first some naming, let’s call the (shared)view in Activity A the origin view and, the (shared)view in Activity B, destination view. Remember, these two views, although they are called shared, they really are two independent view objects that happen to have the same content.

Let’s start…

Activity A — Capture and pass start values

Let’s say that when you transition from Activity A you want to create the visual effect where the shared view translates into its final position and size in Activity B.
The first logical thing would be to capture the start and end values of the shared view in both activities so that we can crunch some numbers later on when translating it into its final position.

Once we are in Activity B, we can only access the end values of the destination view so, what we do is capturing the location and size of our origin view, bundle it and pass it via an intent to Activity B.

It is important to call overridePendingTransition(0,0) right after startActivity() to disable the default transition as we are implementing our own.

The method captureValues(View) would bundle and return the location and size of the view and could be something like this.

Activity B — Start transparent

We are in Activity B now. But we don’t want the user to see any of its layout until we are ready to run the transition animation. The solution is very simple, make sure the activity starts transparent and, make sure the layouts and views are set as INVISIBLE.

Activity B — Prepare the scene

Probably the most important step of all. We are now in Activity B.

First off, you need to make sure you have all the resources ready. Imagine that the shared view is an ImageView and the image resource is loaded from its URL. So you need to make sure that the image is fetched before starting any transition. This is not very hard when using libraries like Picasso, Glide, etc.

The method onUiReady() will prepare the scene and run the transition animation and should only be called once all needed resources are fetched and/or ready.

Prepping the scene is, conceptually, not very complicated. Let’s refresh our use case for a second. 
Two activities, A and B. They both have a view (shared element) that contains the same content (e.g. image). We want that view to transition between the two activities (e.g. it will animate from the initial position in Activity A to its final position in Activity B).

We already have the start values of the shared view — passed via an intent — and now it’s time to capture the end values. But to do so, we need to find the place where our layout is finished but yet not drawn. 
Where exactly is just after layout and before drawing? The framework provides the perfect place for that, onPreDraw().

The method onPreDraw() will be called by the framework right after the view layout and just before drawing so it seems like we have a winner.
To prepare the scene, just capture the end values of the destination view, calculate the deltas w.r.t. the start values passed via the intent, and reposition the view.

We are all set to run the animation.

Activity B — Run the animation

Our destination view in Activity B is now placed and sized at the same location of the origin view in Activity A. So we just run the animation to its final size and position.

And this is all there is to it!

Last bit, run the reverse exit animation when coming back from Activity B to Activity A. You could do that in e.g. onBackPressed() but there might be other places depending on your application.

And that is really all there is to it. And compatible with all Android versions!

This is an real example using this technique in one of my apps ヽ(´ー`)ノ

Remarks

The technique I showed in this post is more or less the same principle followed in the Android framework. But the framework handles some more things that I am ignoring here and that have some implications.
An important one is caused by the following line.

<item name=”android:windowIsTranslucent”>true</item>

That simple line has some implications. The most important is that Activity A in only paused and not stopped, pretty much like when showing a dialog. I will not go into details but, as long as you don’t keep stacking translucent activities on top of each other, it should not have any noticeable impact on performance or present any issue.

Of course, for more complicated transitions I would recommend using the framework API — even if you have to fallback to other behaviours for API level < 21.

If you got this far you should be able to reproduce this in your own app. 
In any case, I leave here a couple of links to the real full java implementation of what I called here Activity B, its layout and some utils that I use in my SpaceNews reader app.

I you liked the article, click the 💚 below so other people will see it here on Medium.