Android — Smooth shared transitions in all android versions

In Android Lollipop (5.0) Google introduced the concept of shared element transitions and these are awesome!! they give a fluid and smooth feel to the apps UI. However it’s not available in support libraries, at least not yet, so i’m gonna show how we can simulate a shared element transition working in all Android versions from Api 10(Gingerbread) the lowest i tested.

What we need

  • Android support library v7

The final result (if the gif is flickering see the video at the end)

Shared element transition

Whats happening here ?

We have 2 activities, the first Activity has 2 image views and the second Activity has 1 image view on the top. When we open the second activity the image view translates and resizes to it’s final position and size, starting from first activity’s image view position and size.

Step by step

  • Capture the size and position on screen off the ImageView in first activity.
  • Pass those values to the second activity.
  • In second activity save ImageView target size and position. We need to do this after the first layout pass is made.
  • Set second activity ImageView size and position to the captured values.
  • Finally resize and translate ImageView in second activity it’s target size and position.

Well it may sound very complicated but it’s not.

Show me the code

First activity

The open() method, as you can see we simply save ImageView x, y, width and height on Intent extras and pass those values to the second activity. Please pay attention to line 39, we need to disable default Android animations otherwise the second activity will open with an animation and we don’t want that. Calling overridePendingTransition(0,0) will do the job.

Second activity

This is where all the magic happens. Let’s analyze it line by line.

34–38, grab the values stored in Intent extras. We will set the ImageView x,y,width and height to match those values.

40, add a global layout listener to the ImageView so we can be notified about layout events. This is very important !! And why ? You may wonder why not set ImageView position and size to the captured values when onCreate() is called, however if we do that how can we know it’s final position and size. Remember during onCreate() the view hasn’t been measured or laid out yet. If we call getWidth() or getX() it returns 0.

The alternative ? Register a GlobalLayoutListener this is called when layout events occur.

52–53, we now know where the ImageView wants to be positioned, save x and y.

56–57, we now know the size of the ImageView, save width and height.

60–61, we can now set the width and height to the values received in the Intent from the first activity(origWidth,origHeight).

64–65, set x and y to the values received in the Intent(origX,origY)

67–68, call requestLayout() this will make another layout pass with the updated size and position. By now we have all the values we need, original values and target values so we can animate the view, set hasBeenMeasured to true.

70, we called requestLayout() which makes another layout pass that will cause out GlobalLayoutListener to be called again, however this time hasBeenMeasured is true.

75–89, we create a resize and translate animation to resize the ImageView to target position and size. This is the animation code:

This will resize and translate the view from view’s x, y, width and height to targetX, targetY, targetWidth and targetHeight. We create a new instance like this:

resizeTranslateAnimation = new ResizeTranslateAnimation(
mImageView, targetWidth, targetHeight, targetX, targetY);

91, finally we remove the GlobalLayoutListener this is important, otherwise the listener will be called again when a layout event occurs and we don’t want that to happen.

This is it, we know have a working “shared element transition” simulation, however when we finish the activity it would be nice to reverse the animation, and we can do it EASILY trust me.

Reversing the animation

This technique is borrowed from StackOverflow thread

By using a reverse interpolator we can reverse our animation with just a line of code

resizeTranslateAnimation.setInterpolator(new ReverseInterpolator());

ReverserInterpolator

Line 106, override onBackPressed() set the reserver interpolator and start the animation. On the animation ends don’t forget to finish() and overridePendingTransition(0,0).

finish();
overridePendingTransition(0, 0);

Last but not least

Second activity must have a translucent background we can do it by using these theme:

<style name="TransparentTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:background">@android:color/transparent</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>

Util class

You probably noticed that i used a Util class in the code, this is needed because some methods setAlpha(), getX() etc… are not available in lower Android versions so we need to improvise.

Here’s the code

Video

Thanks for reading this and hope you guys like it.

Sérgio Serra

Mobile Developer