Customizing transition animations between activities and fragments Android 29.07.2017

To modify an activity transition, use the overridePendingTransition() API for a single occurrence, or declare custom animation values in your application’s theme to make a more global change. To modify a fragment transition, use the onCreateAnimation() or onCreateAnimator() API methods.

Activity

When customizing the transitions from one activity to another, there are four animations to consider: the enter and exit animation pair when a new activity opens, and the entry and exit animation pair when the current activity closes. Each animation is applied to one of the two activity elements involved in the transition. For example, when starting a new activity, the current activity will run the "open exit" animation and the new activity will run the "open enter" animation. Because these are run simultaneously, they should create somewhat of a complementary pair or they may look visually incorrect.

Following is content of file res/anim/activity_open_enter.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
    android:fromDegrees="90" android:toDegrees="0"
    android:pivotX="0%" android:pivotY="0%"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
<alpha
    android:fromAlpha="0.0" android:toAlpha="1.0"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
</set>

Following is content of file res/anim/activity_open_exit.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
    android:fromDegrees="0" android:toDegrees="-90"
    android:pivotX="0%" android:pivotY="0%"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
<alpha
    android:fromAlpha="1.0" android:toAlpha="0.0"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
</set>

Following is content of file res/anim/activity_close_enter.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<rotate
    android:fromDegrees="-90" android:toDegrees="0"
    android:pivotX="0%p" android:pivotY="0%p"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
<alpha
    android:fromAlpha="0.0" android:toAlpha="1.0"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
</set>

Following is content of file res/anim/activity_close_exit.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
    android:fromDegrees="0" android:toDegrees="90"
    android:pivotX="0%p" android:pivotY="0%p"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
<alpha
    android:fromAlpha="1.0" android:toAlpha="0.0"
    android:fillEnabled="true"
    android:fillBefore="true" android:fillAfter="true"
    android:duration="500" />
</set>

What we have created are two "open" animations that rotate the old activity out and the new activity in, clockwise. The complementary "close" animations rotate the current activity out and the previous activity in, counterclockwise. Each animation also has with it a fade-out or fade-in effect to make the transition seem more smooth. To apply these custom animations at a specific moment, we can call the method overridePendingTransition() immediately after either startActivity() or finish(), like so:

// start a new Activity with custom transition
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.activity_open_enter, R.anim.activity_open_exit);

// close the current Activity with custom transition
finish();
overridePendingTransition(R.anim.activity_close_enter, R.anim.activity_close_exit);

This is useful if you need to customize transitions in only a few places. But suppose you need to customize every activity transition in your application; calling this method everywhere would be quite a hassle. Instead, it would make more sense to customize the animations in your application’s theme. Following listing (file res/values/styles.xml) illustrates a custom theme that overrides these transitions globally.

<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light">
    <item name="android:windowAnimationStyle">
        @style/ActivityAnimation</item>
</style>
<style name="ActivityAnimation"
    parent="@android:style/Animation.Activity">
    <item name="android:activityOpenEnterAnimation">
        @anim/activity_open_enter</item>
    <item name="android:activityOpenExitAnimation">
        @anim/activity_open_exit</item>
    <item name="android:activityCloseEnterAnimation">
        @anim/activity_close_enter</item>
    <item name="android:activityCloseExitAnimation">
        @anim/activity_close_exit</item>
</style>
</resources>

By supplying a custom attribute for the android:windowAnimationStyle value of the theme, we can customize these transition animations. It is important to also refer back to the parent style in the framework because these four animations are not the only ones defined in this style, and you don’t want to erase the other existing window animations inadvertently.

Support Fragments

Customizing the animations for fragment transitions is different, depending on whether you are using the Support Library. The variance exists because the native version uses the new Animator objects, which are not available in the Support Library version.

When using the Support Library, you can override the transition animations for a single FragmentTransaction by calling setCustomAnimations(). The version of this method that takes two parameters will set the animation for the add/replace/remove action, but it will not animate on popping the back stack. The version that takes four parameters will add custom animations for popping the back stack as well. Using the same Animation objects from our previous example, the following snippet shows how to add these animations to a FragmentTransaction:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.setCustomAnimations(R.anim.activity_open_enter,
    R.anim.activity_open_exit,
    R.anim.activity_close_enter,
    R.anim.activity_close_exit);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();

setCustomAnimations() must be called before add(), replace(), or any other action method, or the animation will not run. It is good practice to simply call this method first in the transaction block.

If you would like the same animations to run for a certain fragment all the time, you may want to override the onCreateAnimation() method inside the fragment instead. Following listing reveals a fragment with its animations defined in this way.

public class SupportFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater,
        ViewGroup container, Bundle savedInstanceState) {

        TextView tv = new TextView(getActivity());
        tv.setText("Fragment");
        tv.setBackgroundColor(Color.RED);
        return tv;
    }

    @Override
    public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {
        switch (transit) {
            case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
                if (enter) {
                    return AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_in);
                } else {
                    return AnimationUtils.loadAnimation(getActivity(), android.R.anim.fade_out);
                }
            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
                if (enter) {
                    return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_enter);
                } else {
                    return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_close_exit);
                }
            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
            default:
                if (enter) {
                    return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_enter);
                } else {
                    return AnimationUtils.loadAnimation(getActivity(), R.anim.activity_open_exit);
                }
        }
    }
}

How the fragment animations behave has a lot to do with how the FragmentTransaction is set up. Various transition values can be attached to the transaction with setTransition(). If no call to setTransition() is made, the fragment cannot determine the difference between an open or close animation set, and the only data we have to determine which animation to run is whether this is an entry or exit.

To obtain the same behavior as we implemented previously with setCustomAnimations(), the transaction should be run with the transition set to TRANSIT_FRAGMENT_OPEN. This will call the initial transaction with this transition value, but it will call the action to pop the back stack with TRANSIT_FRAGMENT_CLOSE, allowing the fragment to provide a different animation in this case. The following snippet illustrates constructing a transaction in this way:

FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
// set the transition value to trigger the correct animations
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();

Fragments also have a third state that you won’t find with activities, and it is defined by the TRANSIT_FRAGMENT_FADE transition value. This animation should occur when the transition is not part of a change, such as add or replace, but rather the fragment is just being hidden or shown. In our example we use the standard system-fade animations for this case.

Native Fragments

If your application is targeting API Level 11 or later, you do not need to use fragments from the Support Library, and in this case the custom animation code works slightly differently. The native fragment implementation uses the newer Animator object to create the transitions rather than the older Animation object. This requires a few modifications to the code; first of all, we need to define all our XML animations with Animator instead.

Following is content of file res/animator/fragment_exit.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
    android:valueFrom="0" android:valueTo="-90"
    android:valueType="floatType"
    android:propertyName="rotation"
    android:duration="500"/>
<objectAnimator
    android:valueFrom="1.0" android:valueTo="0.0"
    android:valueType="floatType"
    android:propertyName="alpha"
    android:duration="500"/>
</set>

Following is content of file res/animator/fragment_enter.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
    android:valueFrom="90" android:valueTo="0"
    android:valueType="floatType"
    android:propertyName="rotation"
    android:duration="500"/>
<objectAnimator
    android:valueFrom="0.0" android:valueTo="1.0"
    android:valueType="floatType"
    android:propertyName="alpha"
    android:duration="500"/>
</set>

Following is content of file res/animator/fragment_pop_exit.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
    android:valueFrom="0" android:valueTo="90"
    android:valueType="floatType"
    android:propertyName="rotation"
    android:duration="500"/>
<objectAnimator
    android:valueFrom="1.0" android:valueTo="0.0"
    android:valueType="floatType"
    android:propertyName="alpha"
    android:duration="500"/>
</set>

Following is content of file res/animator/fragment_pop_enter.xml.

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<objectAnimator
    android:valueFrom="-90" android:valueTo="0"
    android:valueType="floatType"
    android:propertyName="rotation"
    android:duration="500"/>
<objectAnimator
    android:valueFrom="0.0" android:valueTo="1.0"
    android:valueType="floatType"
    android:propertyName="alpha"
    android:duration="500"/>
</set>

Apart from the slightly different syntax, these animations are almost identical to the versions we created previously. The only other difference is that these animations are set to pivot around the center of the view (the default behavior) rather than the top-left corner.

As before, we can customize a single transition directly on a FragmentTransaction with setCustomAnimations(); however, the newer version takes our Animator instances. The following snippet shows this with the newer API:

FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.setCustomAnimations(R.animator.fragment_enter,
    R.animator.fragment_exit,
    R.animator.fragment_pop_enter,
    R.animator.fragment_pop_exit);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();

If you prefer to set the same transitions to always run for a given subclass, we can customize the fragment as before. However, a native fragment will not have onCreateAnimation(), but rather an onCreateAnimator() method instead. Have a look at following listing, which redefines the fragment we created using the newer API.

public class NativeFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        TextView tv = new TextView(getActivity());
        tv.setText("Fragment");
        tv.setBackgroundColor(Color.BLUE);
        return tv;
    }

    @Override
    public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
        switch (transit) {
            case FragmentTransaction.TRANSIT_FRAGMENT_FADE:
                if (enter) {
                    return AnimatorInflater.loadAnimator(getActivity(), android.R.animator.fade_in);
                } else {
                    return AnimatorInflater.loadAnimator(getActivity(), android.R.animator.fade_out);
                }
            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
                if (enter) {
                    return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_pop_enter);
                } else {
                    return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_pop_exit);
                }
            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
            default:
                if (enter) {
                    return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_enter);
                } else {
                    return AnimatorInflater.loadAnimator(getActivity(), R.animator.fragment_exit);
                }
        }
    }
}

Again, we are checking for the same transition values as in the support example; we are just returning Animator instances instead. Here is the same snippet of code to properly begin a transaction with the transition value set:

FragmentTransaction ft = getFragmentManager().beginTransaction();
// set the transition value to trigger the correct animations
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.replace(R.id.container_fragment, fragment);
ft.addToBackStack(null);
ft.commit();

The final method you can use to set these custom transitions globally for the entire application is to attach them to your application’s theme. Following listing (file res/values/styles.xml) shows a custom theme with our fragment animations applied.

<resources>
<style name="AppTheme" parent="android:Theme.Holo.Light">
    <item name="android:windowAnimationStyle">
    @style/FragmentAnimation</item>
</style>
<style name="FragmentAnimation" parent="@android:style/Animation.Activity">
    <item name="android:fragmentOpenEnterAnimation">
        @animator/fragment_enter</item>
    <item name="android:fragmentOpenExitAnimation">
        @animator/fragment_exit</item>
    <item name="android:fragmentCloseEnterAnimation">
        @animator/fragment_pop_enter</item>
    <item name="android:fragmentCloseExitAnimation">
        @animator/fragment_pop_exit</item>
    <item name="android:fragmentFadeEnterAnimation">
        @android:animator/fade_in</item>
    <item name="android:fragmentFadeExitAnimation">
        @android:animator/fade_out</item>
</style>
</resources>

As you can see, the attributes for a theme’s default fragment animations are part of the same windowAnimationStyle attribute. Therefore, when we customize them, we make sure to inherit from the same parent so as not to erase the other system defaults, such as activity transitions. You must still properly request the correct transition type in your FragmentTransaction to trigger the animation.

If you wanted to customize both the activity and fragment transitions in the theme, you could do so by putting them all together in the same custom style (file res/values/styles.xml).

<resources>
    <style name="AppTheme" parent="android:Theme.Holo.Light">
        <item name="android:windowAnimationStyle">
        @style/TransitionAnimation</item>
    </style>
    <style name="TransitionAnimation"
            parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">
            @anim/activity_open_enter</item>
        <item name="android:activityOpenExitAnimation">
            @anim/activity_open_exit</item>
        <item name="android:activityCloseEnterAnimation">
            @anim/activity_close_enter</item>
        <item name="android:activityCloseExitAnimation">
            @anim/activity_close_exit</item>
        <item name="android:fragmentOpenEnterAnimation">
            @animator/fragment_enter</item>
        <item name="android:fragmentOpenExitAnimation">
            @animator/fragment_exit</item>
        <item name="android:fragmentCloseEnterAnimation">
            @animator/fragment_pop_enter</item>
        <item name="android:fragmentCloseExitAnimation">
            @animator/fragment_pop_exit</item>
        <item name="android:fragmentFadeEnterAnimation">
            @android:animator/fade_in</item>
        <item name="android:fragmentFadeExitAnimation">
            @android:animator/fade_out</item>
    </style>
</resources>

Adding fragment transitions to the theme will work only for the native implementation. The Support Library cannot look for these attributes in a theme because they did not exist in earlier platform versions.

Activity Shared Element Transition

Before Android 5.0 Lollipop animations between activities weren't a strong point of Android apps. There weren't many ways to create a smooth and engaging experience for the user. A lot of apps looked the same when it comes to activity transitions. Fortunately Android 5.0 brought new Transitions API.

Let's assume that we have activity with a grid of images from phone memory. Mentioned activity can be named MainActivity. Once you click on the image, app opens new activity, let's say DetailActivity. Image thumbnail from grid starts to increase it's bounds and move to the center of the screen. Old activity will be replaced by new activity at the same time. After pressing the return button, all the things will change in reverse order.

Activities should extend AppCompatActivity class as you can see in example app.

In layout resource file remember to name transition:

Snippet from item_view.xml.

<ImageView
    android:id="@+id/image"
    android:transitionName="@string/image_transition"
    android:adjustViewBounds="true"
    android:scaleType="centerCrop"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Next, you should do the same in layout resource file that we want to show in detail activity:

Snippet from activity_detail.xml

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

It is very important to use the same name for the pair of views, which we want to animate.

The last step is open the activity in new way:

ActivityOptionsCompat options = ActivityOptionsCompat
        .makeSceneTransitionAnimation(context,
                                    holder.image, 
                                    context.getString(R.string.image_transition));
Intent intent = new Intent(context, DetailActivity.class);
ActivityCompat.startActivity(context, intent, options.toBundle());

What if you want to do something special with your image?

It can be basically resizing, because ImageView can load an image of maximum size 4096x4096. Just put this line:

supportPostponeEnterTransition();

in onCreate method and this line:

supportStartPostponedEnterTransition();

when you are ready to open new activity. It is very useful, when image is loading from url, for example by Glide:

@Override
protected void onCreate(Bundle savedInstanceState) {
   ...
   supportPostponeEnterTransition();

   Glide.with(this)
      ...
      .listener(new RequestListener<String, GlideDrawable>() {
         @Override
         public boolean onResourceReady(GlideDrawable resource, String model, 
                 Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
            DetailsActivity.this.supportStartPostponedEnterTransition();
            return false;
         }
      })
      ...

Following is example of custom transition animation

public class MainActivity extends AppCompatActivity  {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
        setExitTransition();
        setReenterTransition();

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }        

    public void openActivity(View v) {
        Intent i = new Intent(this, DetailActivity.class);
        Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(this).toBundle();
        ActivityCompat.startActivity(this, i, b);
    }    

    private void setExitTransition() {
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Transition transition = new Fade();
            transition.setDuration(600);
            transition.setInterpolator(new FastOutLinearInInterpolator());
            getWindow().setExitTransition(transition);
        }
    }

    private void setReenterTransition() {
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Transition transition = new Explode();
            transition.setDuration(600);
            transition.setInterpolator(new FastOutLinearInInterpolator());
            getWindow().setReenterTransition(transition);
        }
    }
}