Applying meaningful motion on Android

How to apply meaningful and delightful motion in a sample Android app

According to Material Design specs:

“Motion design can effectively guide the user’s attention in ways that both inform and delight. Use motion to smoothly transport users between navigational contexts, explain changes in the arrangement of elements on a screen, and reinforce element hierarchy.”

The app mustn’t let the user lose the focus on the content during animations. There’s a post from John Sherwin saying there are two kinds of animation: the ‘sparkle animation’ and the ‘obvious animation’:

“Sparkle animations are cool to look at, but generally pull our attention away from content or important tasks.” “Obvious animations are exactly what they sound like! They… obey the laws of physics by accelerating, moving and then decelerating in a way that seems natural to us. It’s so natural that to an average user they become almost invisible.”

We must apply those animations carefully to avoid the app become a true Pixar animation movie.

The Sample

This sample contains motions that make the user stay focused on the interacted element and content

It provides three activities:

Main Activity

It contains the tiles grid of six sample images.

The GridLayout widget is setup with 3 columns and 3 rows and it’s used as container of image views.

The first view of second row is spanning 2 columns and the third row is spanning 3 columns.

Detail Activity

Shows the details of selected image: name and description.

Contains a FloatingActionButton to show sharing options.

It uses scrolling techniques detailed at this post.

Sharing Activity

Shows the options to share the content.

It’s just a translucent activity. This yellow shape is a drawable defined as image source of a ImageView.

Custom Activity Transitions

“Activity transitions in material design apps provide visual connections between different states through motion and transformations between common elements” — Android Developer’s page

Before Android Lollipop (API Level 21) we could only customize the activity transition animation over entire activity. All views were animated together. But now we can specify how each view is animated during that transition.

We can specify custom animations for enter/return and exit/reenter transitions and for transitions of shared elements (also called Hero Transition) between activities.

An enter transition determines how views move into the initial scene of the started activity. A return transition is the opposite of this, it determines how views move out of the scene when the activity is finished, for example after a call to Activity.finishAfterTransition().

An exit transition determines how views move out of the scene when starting a new activity. A reenter transition determines how views move into scene when returning from a previously-started activity.

A shared elements transition determines how views are shared between two activities transition.

By default, all Activity transitions are defined as:

@transition/move is a set of these transitions:

changeBounds — Animates the changes in layout bounds of target views.

changeClipBounds — Animates the changes in clip bounds of target views.

changeTransform — Animates the changes in scale and rotation of target views.

changeImageTransform — Animates changes in size and scale of target images.

Activity transition definitions can be declared into theme style and our res/values/styles.xml contains all this:

MainActivity’s theme

@transition/main_exit is the transition used when the MainActivity exit from the scene. We’re using the Explode transition that moves views out from the center of the scene.

Tip: We’re excluding the status bar and navigation bar from that transition once they don’t change their contents.

@transition/main_reenter is the transition used when the DetailActivity come back to MainActivity. In this case we’re using a Slide transition from top.

DetailActivity’s theme

The windowAllowEnterTransitionOverlap attribute controls how the enter transition overlaps with the exit transition of the calling activity. When true, the transition will start as soon as possible. When false, the transition will wait until the exiting transition completes before starting. We declared that detail’s enter transition mustn’t overlap the main’s exit transition:

@transition/detail_enter is the transition used when the DetailActivity is entered to the scene.

We’re using two kinds of transition:

Slide transition — Moves views in from one of the edges of the scene. We chose slide from the bottom.

Fade transition — Adds a view from the scene by changing its opacity.

Tip: We’re including just cardview into Slide transition. The Fade transition won’t animates the status bar, the navigation bar neither the cardview.

SharingActivity’s theme

@transition/sharing_shared_element_enter is the Share Button animation. We’re using arcMotion to apply an arc effect during the animation path.

arcMotion is a transition that generates a curved path along an arc on an imaginary circle containing the two points. To force curvature of the path, minimumHorizontalAngle may be used to set the minimum angle of the arc between two points.

Transition from Main screen to Detail screen

When user clicks on some image item, we must start the DetailActivity with some information that indicates we’re starting a Customized Activity Transition.

In MainActivity we have:

The ActivityOptions.makeSceneTransitionAnimation method create an object containing information about our scene transition animation:

this — The Activity whose window contains the shared elements.

view — The shared view to transition from Main to Detail Activity.

getString(R.string.picture_transition_name) — The identifier of shared element. Defined as String resource to be used in both Activity.

But we have to pass more data to DetailActivity in order to show the chosen image and its details. In MainActivity we pass the picture and title info to setup our CollapsingToolbarLayout and ImageView in DetailActivity:

To finish the our motion from Main to Detail we just scale up the share button when the transition is ended.

Tip: We’re checking the savedInstanceState to starting that animation only when the Activity is created, not when it is recreated.

But if we are scaling up the share button when the Activity is opened then we have to scale down when the activity is finished.

onBackPressed() method is overriden to start the scale down animation and then, when that animation is ended, the supportFinishAfterTransition() method is called to start the exiting transition and finish the activity.

Transition from Detail screen to Sharing screen

When user clicks on Share Button we must start the SharingActivity. Similarly to MainActivity’s picture click, we make a scene transition animation but in this case our shared element is the share button.

In SharingActivity we have to setup initial states before the animation begin:

When the shared element enter transition has ended:

We remove that listener — To prevent call this method again when we back to DetailActivity Reveal the background — Using ViewAnimationUtils.createCircularReveal Show the items — Animation of alpha from 0 to 1 and using a delay for each item.

onBackPressed() method is overriden to start the hide animation of item and background.

When background hiding animation has ended, the supportFinishAfterTransition() method need to be called to start the exiting transition and finish the Activity.

Item share click

The pure Transition Framework was added since Android KitKat (API Level 19). This framework animates the views at runtime by changing some of their property values over time. One of the features is the ability of running animations based on the changes between starting and ending view property values.

First of all, we need to load our transition from xml. We do this by the TransitionInflater.

We have to add a listener to finish the Activity only when this transition is ended.

The TransitionManager.beginDelayedTransition() method will capture current values in the scene root and then post a request to run a transition on the next frame. After that, we can change the view property values:

Item chosen: This item will translate to the center of screen. So, we’re adding a rule CENTER_IN_PARENT to RelativeLayout params. Rest of items: All that items will fade out. In this case we’re changing the visibility property to INVISIBLE state. Background: The yellow background must scale to cover the entire screen. We’re doing some calculus to get the necessary scale to cover this. And then, we use the Matrix object to post that scale.

Conclusion

We have to take care when we are applying motion in our application to avoid a bad User Experience.

In this sample we saw how to build beautiful apps with meaningful and delightful motion.

This sample code is available at https://github.com/andremion/UI-Motion

And below are some links where you can go deeper into Android Motion: