iOS to Android: Slide In Animations

Photo by Masakaze Kawakami on Unsplash

Porting an iOS app to Android means frequently translating between the divergent UI paradigms of the two operating systems.  Both platforms encourage developers to follow certain interface guidelines, but clients sometimes prefer replicating a familiar interface.  Thankfully, Android offers fairly deep customization.

For one port, the Android app needed to use the same view transition animation as its iOS counterpart.

On iOS, the standard navigation stack defaults to animating a detail page transition (called by [UINavigationController  pushViewController:detailController animated:YES]) by sliding the detail view in from the right, and the root view out to the left.  Navigating back to the root view ([UINavigationController popViewControllerAnimated:YES]) reverses the animation, sliding the detail view out to the right and the root view in from the left.

On Android, the same transition (handled through a FragmentTransaction) defaults to a zoom animation, or sometimes a slide up animation.  How can Android mimic iOS’ transition animation?

Java code

Android’s FragmentTransaction class includes methods to specify the animations, which themselves are defined in XML files.  The Java code is fairly simple:

FragmentTransaction transaction =
   getSupportFragmentManager().beginTransaction();

transaction.setCustomAnimations(
   R.anim.slide_in_from_right, R.anim.slide_out_to_left,
   R.anim.slide_in_from_left, R.anim.slide_out_to_right);

transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);

transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.commit();

The key here is to call setCustomAnimations with four separate animation files (or, to be more exact, with four resource ID integers representing the animation files).

The first two are used for pushing a detail view:

  1. animation for the detail view being presented
  2. animation for the root view being hidden (while the detail view is transitioning on screen)

The last two are used for popping a detail view:

  1. animation for the root view being brought back
  2. animation for the detail view being hidden (while the root view is transitioning on screen)

Animation XML

To fairly closely mimic the iOS transitions, the animation files need to specify a few attributes:

  • The interpolator, which defines the acceleration curve (e.g. linear, decelerate, etc.)
  • The duration, which defines how long the animation lasts
  • The translation X and Y values, which define where the view starts from and moves to

Here’s res/anim/slide_in_from_right.xml, used for presenting the detail view:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:interpolator="@android:anim/decelerate_interpolator"
   android:shareInterpolator="true">

   <translate
         android:duration="@android:integer/config_shortAnimTime"
         android:fromXDelta="100%"
         android:toXDelta="0%"
         android:fromYDelta="0%"
         android:toYDelta="0%"/>
</set>

The interpolator value will cause the animation to start quickly and then decelerate. The animation duration is an Android constant, which is set to 200 ms. The translation deltas mean that the view’s x coordinate will start at 100% of the containing view (i.e. off the right side of the screen), and end up on the left side of the containing view.

Its match in the pair, res/anim/slide_out_to_left.xml, differs only in the x delta values:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:interpolator="@android:anim/decelerate_interpolator"
   android:shareInterpolator="true">

   <translate
         android:duration="@android:integer/config_shortAnimTime"
         android:fromXDelta="0%"
         android:toXDelta="-100%"
         android:fromYDelta="0%"
         android:toYDelta="0%"/>
</set>

It’s important that the interpolator and duration values be the same for each pair of animations, or the view transitioning out and the view transitioning in won’t overlap properly.

The animations for popping the detail view are very similar, though a linear interpolator more closely approximates the iOS animation.

res/anim/slide_in_from_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:interpolator="@android:anim/linear_interpolator"
   android:shareInterpolator="true">

   <translate
         android:duration="@android:integer/config_shortAnimTime"
         android:fromXDelta="-100%"
         android:toXDelta="0%"
         android:fromYDelta="0%"
         android:toYDelta="0%"/>
</set>

res/anim/slide_out_to_right.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
   android:interpolator="@android:anim/linear_interpolator"
   android:shareInterpolator="true">

   <translate
         android:duration="@android:integer/config_shortAnimTime"
         android:fromXDelta="0%"
         android:toXDelta="100%"
         android:fromYDelta="0%"
         android:toYDelta="0%"/>
</set>

Result

The result is comparable to the iOS navigation stack animations (WordPress sometimes doesn’t want this gif to loop, in which case open it in a new window to see it play through):

Custom navigation animation in GuitarTricks Android app.


Custom navigation animation in GuitarTricks Android app.

The iOS animation is slightly more refined, in that it applies a gray mask to the root view, with the darkness increasing as the amount of the root view visible decreases. The mask is also very dark at the edge between the two views, which takes on the appearance of a drop shadow. It’s likely possible to replicate these extra details in Android, either through additional XML animation elements or through custom code.

Noah Harrison

Noah Harrison

Senior software engineer at Art+Logic.
Noah Harrison

Latest posts by Noah Harrison (see all)

Tags:

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.