Shared Element Transitions

Ayush Khare
4 min readNov 13, 2016

--

Designing an App to look beautiful is a very crucial part in development. With the introduction of Material Design in Android, there are several features that can make an App look beautiful.

Shared Element Transitions were introduced with Android L. It looks very complicated but the implementation is like a walk in the park. So let’s see how this animation works in 3 simple steps.

  • Step 1: First(Calling) Activity sends the view that needs to shared between the activities/fragments.
  • Step 2: Assign a common transition name for both views that are sharing the animation.
  • Step 3: Start the activity with animation.

Let’s look at some sample pieces of code to make the above steps more clear:

Intent intent = new Intent(mContext, NextActivity.class);
intent.putExtra("image", movie.getImage());
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) mContext, (View)imageView, getString(R.string.image_transition);
startActivity(intent, options.toBundle());

The ActivityOptionsCompat class is responsible for this transition animation. It calls a function makeSceneTransitionAnimation which accepts 3 parameters in the above case:

  • Context
  • View to be animated
  • Transition name

The only thing to be considered here is that you provide a unique transition name to views in both the layouts. You can provide the transition name in xml like below:

First Activity Layout:

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/image_transition"/>

Second Activity Layout:

<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:transitionName="@string/image_transition"/>

That’s it! This is how simple it is. Now let’s look at different complications that you can deal with:

What if you need more than one element to be shared between the activities?

Simple, just provide all the views to be shared to the intent, each with a unique transition name like below:

Intent intent = new Intent(mContext, NextActivity.class);
intent.putExtra("image", movie.getImage());
intent.putExtra("title", movie.getTitle());
intent.putExtra("genre", movie.getGenre());
intent.putExtra("year", movie.getYear());
Pair<View, String> p1 = Pair.create((View) imageView, mContext.getString(R.string.image_transition));Pair<View, String> p2 = Pair.create(((View) titleView), mContext.getString(R.string.title_transition));Pair<View, String> p3 = Pair.create(((View) genreView), mContext.getString(R.string.genre_transition));Pair<View, String> p4 = Pair.create(((View) yearView), mContext.getString(R.string.year_transition));ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) mContext, p1, p2, p3, p4);
mContext.startActivity(intent, options.toBundle());

We can use the Pair class to send the view and it’s transition name to makeSceneTransitionAnimation function along with the context. You can pass as many views as you want. Do remember to provide a unique transition name for each shared element!

Now, there are two factors you have to consider to make this animation work smoothly:

  • The amount of time it takes for the called activity to load it’s data: if the shared elements’ final appearance within the activity depends on asynchronously loaded data, there is a chance that the framework might automatically start the shared element transition before that data is delivered back to the main thread.
  • The complexity and depth of the called activity’s layout: the more complex the layout, the more time it will take to determine the shared element’s position and size on the screen.

The Activity transition API gives a way to tackle this problem too!

So to temporarily prevent the animation from starting, we can postpone it and start again once the data is loaded in the shared element view. Let’s have a look on how this can be done:

protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_next_activity);

//Postpone the enter transition until image is loaded
postponeEnterTransition();
}

We can call the postponeEnterTransition() function to delay the animation temporarily, in the receiving activity’s onCreate() method. Later, when you know for sure that the data is loaded, you can call startPostponedEnterTransition() function to resume the animation.

Problem 1: Make sure data is loaded

Glide.with(this)
.load(imageUrl)
.listener(new RequestListener<String, GlideDrawable>() {
@Override
public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
return false;
}

@Override
public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
//Image successfully loaded into image view
scheduleStartPostponedTransition(imageView);
return false;
}
})
.into(imageView);

Suppose using the Glide Library for image loading, it provides a listener which tells when the image is done loading. So after the data is loaded we have to make sure our view is drawn. Let’s take a look at what is happening inside scheduleStartPostponedTransition() function.

Problem 2: Make sure view is drawn

private void scheduleStartPostponedTransition(final ImageView imageView) {
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
startPostponedEnterTransition();
return true;
}
});
}

To make sure our shared view is drawn, we use the ViewTreeObserver class. After we make sure that our data is loaded and our view is drawn, we can call startPostponedEnterTransition() function to resume our animation.

Points to note:

  • Never forget to call startPostponedEnterTransition() after calling postponeEnterTransition(). Forgetting to do so will leave your application in a state of deadlock, preventing the user from ever being able to reach the next Activity screen.
  • Never postpone a transition for longer than a fraction of a second. Postponing a transition for even a fraction of a second could introduce unwanted lag into your application, annoying the user and slowing down the user experience.

That’s it, enjoy your animation! Make your app look more beautiful!

You can download the full app here. You can also customise your animation, I would prefer you reading this for more.

--

--

Ayush Khare
Ayush Khare

No responses yet