Lipika Gupta

How to apply Shared Element Transitions in Android

Doesn’t it look cool when one view appears to move across screens without breaking the continuity of motion? It just adds to the flair of your app, thus improving the app’s UX.

android app

Now, this can be achieved using Shared Element Transitions; but here is the catch. This transition effect is available only on devices running on Lollipop (Android 5.0 – API level 21) and higher.

Shared elements transitions were introduced in Android 5.0 to make view transitions across screens more seamless and easy to implement. Using this transition, the switch between Activities or Fragments seems more natural and unforced.

Before Android 5.0, transition effects across Activities were available, but they would animate the entire root view of our screen. Using shared element transitions, we can animate any number of views, regardless of their view hierarchies.

Now, let’s see how we can implement shared element transitions in our Android apps-

Step 1 : Enable Window Content Transitions in styles.xml

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- enable window content transitions -->
    <item name="android:windowContentTransitions">true</item>
</style>

 

Step 2: Set a Common Transition Name for Respective Views on Both Screens

For the transition to work across screens, you have to assign a common transition name to the shared elements (views) in both layouts. The views don’t have to be of the same type or have the same id, only the transition name must be same.

The transition name can be set using the android:transitionName attribute in xml or using the setTransitionName() method in Java.

In the activity_home.xml:

<LinearLayout ...>

    <ImageView
        android:id="@+id/iv_list_object_image"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_margin="5dp"
        android:transitionName="object_image" />

        ...
</LinearLayout>

 

In the activity_object_detail.xml:

<RelativeLayout ...>

    <ImageView
        android:id="@+id/iv_detail_object_image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:transitionName="object_image" />

        ...
</RelativeLayout>

 

Step 3: Open Activity with Element Transition

In order to get the transition effect, you have to specify a bundle of the shared elements and view from the source activity while starting the target activity.

Case I : There is only a single shared element transition that you want to achieve:

Intent intent = new Intent(HomeActivity.this, ObjectDetailActivity.class);
intent.putExtra(ObjectDetailActivity.EXTRA_OBJECT, object);
ActivityOptionsCompat options = ActivityOptionsCompat.
		makeSceneTransitionAnimation(this, mObjectIV, "object_image");
startActivity(intent, options.toBundle());

 

Case II : There are multiple shared element transitions that you want to achieve:

Intent intent = new Intent(HomeActivity.this, ObjectDetailActivity.class);
intent.putExtra(ObjectDetailActivity.EXTRA_OBJECT, object);
Pair<View, String> p1 = Pair.create((View)mObjectIV, "object_image");
Pair<View, String> p2 = Pair.create((View)mObjectNameTV, "object_name");
ActivityOptionsCompat options = ActivityOptionsCompat.
		makeSceneTransitionAnimation(this, p1, p2);
startActivity(intent, options.toBundle());

When we specify the source view along with its corresponding transition name, it ensures that even if multiple views exist in the the source view hierarchy with the same transition name, it picks the correct view to start the animation from.

While specifying multiple shared elements transitions, make sure that you import android.support.v4.util.Pair. Please ensure that you do not overdo the transitions, as that can distract the user and degrade the user experience.

Step 4: Close Activity with Reverse Element Transition

In order to get the reverse element transition effect while finishing the second activity, you need to call the Activity.supportFinishAfterTransition() method instead of the Activity.finish() method. Also, you need to make sure that you override the Activity finish behavior everywhere in your activity, for example if you have a back button in your Toolbar or if the user presses device’s back button.

@Override
public void onBackPressed() {
    //To support reverse transitions when user clicks the device back button
    supportFinishAfterTransition();
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        //To support reverse transition when user clicks the action bar's Up/Home button
        case android.R.id.home:
            supportFinishAfterTransition();
            return true;
    }
    return super.onOptionsItemSelected(item);
}

 

Shared Elements Transitions with Fragments

We can achieve shared elements transitions with Fragments as well.

Step 1: Set a Common Transition Name for Respective Views on Both Screens

In the fragment_home.xml:

<LinearLayout ...>

    <ImageView
        android:id="@+id/iv_list_object_image"
        android:layout_width="75dp"
        android:layout_height="75dp"
        android:layout_margin="5dp"
        android:transitionName="object_image" />

        ...
</LinearLayout>

 

In the fragment_object_detail.xml:

<RelativeLayout ...>

    <ImageView
        android:id="@+id/iv_detail_object_image"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:transitionName="object_image" />

        ...
</RelativeLayout>

 

Step 2: Define a Custom Transition:

change_bounds.xml in res/transition:

xml version="1.0" encoding="utf-8"?>
<transitionSet>
    <changeBounds />
</transitionSet>

 

change_image_transform.xml in res/transition:

xml version="1.0" encoding="utf-8"?>
<transitionSet>
    <changeImageTransform />
</transitionSet>

 

Step 3: Specify the Shared Elements Transition in FragmentTransaction:

Transition changeImageTransform = TransitionInflater.from(this).
        inflateTransition(R.transition.change_image_transform);
Transition changeBoundsTransform = TransitionInflater.from(this).
        inflateTransition(R.transition.change_bounds);

// First Fragment
HomeFragment homeFragment = new HomeFragment();
//Second Fragment
ObjectDetailFragment objectDetailFragment = new ObjectDetailFragment();

// Setup exit transition on HomeFragment
homeFragment.setSharedElementReturnTransition(changeImageTransform);
homeFragment.setExitTransition(changeBoundsTransform);

// Setup enter transition on ObjectDetailFragment
objectDetailFragment.setSharedElementEnterTransition(changeImageTransform);
objectDetailFragment.setEnterTransition(changeBoundsTransform);

// Find the shared element (in MainFragment)
ImageView objectIV = (ImageView) findViewById(R.id.iv_list_object_image);

// Add ObjectDetailFragment by replacing the HomeFragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction()
        .replace(R.id.container, objectDetailFragment)
        .addToBackStack("transaction_tag")
        .addSharedElement(objectIV, "object_image");

// Apply the transaction
ft.commit();

 

Custom Shared Elements Transitions:

In Android Lollipop (Android 5.0), the default shared elements transition is a combination of 4 transitions:

  1. Change Bounds – It captures the layout bounds of target views before and after the scene change and animates those changes during the transition.
  2. Change Transform – It captures scale and rotation for Views before and after the scene change and animates those changes during the transition.
  3. Change Image Transform – It captures an ImageView’s matrix before and after the scene change and animates it during the transition.
  4. Change Clip Bounds – It captures the getClipBounds() before and after the scene change and animates those changes during the transition.

In most cases, the default transition is sufficient. However, there might be cases in which you might want to customize the default behavior and define your own custom transitions.

<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <!-- enable window content transitions -->
    <item name="android:windowContentTransitions">true</item>

    <!-- specify window enter and exit transitions -->
    <item name="android:windowEnterTransition">@transition/custom_window_transition</item>
    <item name="android:windowExitTransition">@transition/custom_window_transition</item>

    <!-- specify shared element enter and exit transitions -->
    <item name="android:windowSharedElementEnterTransition">@transition/custom_shared_element_transition</item>
    <item name="android:windowSharedElementExitTransition">@transition/custom_shared_element_transition</item>
</style>

 

custom_window_transition.xml:

xml version="1.0" encoding="utf-8"?>
<transitionSet>
    <changeBounds />
</transitionSet>

 

custom_shared_element_transition.xml:

xml version="1.0" encoding="utf-8"?>
<transitionSet>
    <changeImageTransform />
</transitionSet>

 

You can set the window content transitions at runtime by calling the Window.requestFeature() method.

getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
getWindow().setEnterTransition(new Explode());
getWindow().setExitTransition(new Explode());

 

Exclude Elements from Window Content Transitions

Sometimes you might want to exclude the use of the status bar, ActionBar and navigation bar from the animation sequence. This might be particularly required when your shared elements are drawn on top of these views.

You can achieve this by excluding these elements from the transitions. This can be done by adding a <target> tag and specifying the ID of the element you want to exclude.

custom_transition.xml:

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:slideEdge="left"
    android:duration="1500">

    <targets>
        <!-- Specify the status bar ID if it needs to be excluded -->
        <target android:excludeId="@android:id/statusBarBackground"/>
        <!-- Specify the navigation bar ID if it needs to be excluded -->
        <target android:excludeId="@android:id/navigationBarBackground"/>
        <!-- Specify the AppBarLayout ID if custom toolbar is used that needs to be excluded -->
        <target android:excludeId="@id/app_bar_layout" />
    </targets>

</slide>

 

Shared Elements Transitions with Asynchronous Data Loading

There might be cases when the shared elements require data that might be loaded from a web API or URL. The most common example is when a URL needs to be loaded into an ImageView which also happens to be the shared element we want to animate. However, the shared element transition might get started by the framework before that data is received and rendered.

We can overcome this by temporarily delaying the transition until we know that the shared elements have been rendered with the fetched data.

We can delay the shared element transition by calling postponeEnterTransition() (For API >= 21) or supportPostponeEnterTransition() (For API < 21) in your second Activity’s onCreate() method.

Once you know that the shared elements have been rendered with the data, you can call startPostponedEnterTransition() (For API >= 21) or supportStartPostponedEnterTransition() (For API < 21) to resume the paused transition.

We can start the paused transition in an onPreDrawListener which is called after the view layout and before the view is about to be drawn.

supportPostponeEnterTransition();

mProfileIV.getViewTreeObserver().addOnPreDrawListener(
    new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            mProfileIV.getViewTreeObserver().removeOnPreDrawListener(this);
            supportStartPostponedEnterTransition();
            return true;
        }
    }
);

 

Results

You can expect to see something like this once you are done with all of the steps above.

Default Transition

 

Change Bounds Transition with Bounce Interpolator

 

Change Bounds Transition with Anticipate Interpolator

 

Change Bounds Transition with Anticipate-Overshoot Interpolator

 

I hope this blog helps you in adding shared elements transitions to your Android app to just give it that sleek and smooth look. In case you face any issues, please feel free to get in touch with me by dropping in your comments.

Thank you for reading my blog.

 

Related Articles

#Tech

NHibernate, Linq and Lazy Collections

For the past few months we have been busy building a services framework for one of our clients so that they can build all of their enterprise web services without ever having to worry about the cross cutting concerns and... Read more
#Tech

Page Redirects using Spring.NET

Who is responsible for page redirects in ASPNET MVP – The View or the Presenter None of the above, it is you :) On a serious note, it is the View as one shouldn’t pollute the Presenter with the page navigation... Read more