After seeing an inspiring talk at Droidcon London, I decided to dig deep into motion on Android. From this, I’ve put together my findings to help make both Developers & Designers aware of just how easy it is to add beautiful motion to your Android applications.
If you wish to try out these animations for yourself, all of these examples are packaged into an Android app on Github.
I love motion, not only does it boost engagement but it’s instantly noticeable. Think of the apps you use that feature motion design and how pleasing, satisfying, fluent and natural they feel to experience.
Now compare it to those apps you love that lack those same feelings.
Little things make a big difference
We can make use of these motion effects in a number of ways to:
- Transport users through navigational context
- Reinforce elemental hierarchy
- Explain changes between components displayed on screen
The aim of this article is to show you just how easy it is to implement meaningful motion into your applications – so let’s get started.
Providing feedback when the user touches the screen helps to communicate in a visual form, that an interaction has been made. These animations should not distract the user, but be enough for them to enjoy, gain clarity from and encourage further exploration.
The Android framework provides ripple states for this level of feedback which can be used by setting a view’s background to one of the following:
- ?android:attr/selectableItemBackground – Show a ripple effect within the bounds of the view.
- ?android:attr/selectableItemBackgroundBorderless – Show a ripple effect extending the bounds of the view.
View Property Animator
The ViewPropertyAnimator was introduced at API level 12, allowing you to simply and efficiently perform animated operations (in parallel) on a number of view properties using a single Animator instance.
- alpha() – Set the alpha value to be animated to
- scaleX() & scaleY() – Scales the view on it’s X and / or Y axis
- translationZ() – Translates the view on its Z axis
- setDuration() – Sets the duration of the animation
- setStartDelay() – Sets the delay on the animation
- setInterpolator() – Sets the animation interpolator
- setListener() – Set a listener for when the animation starts, ends, repeats or is cancelled.
Note: When a listener has been set on a view, if you carry out other animations on that same view and don’t wish to use that callback then you must set the listener to null.
It’s both simple and tidy for us to implement programatically:
Note: We don’t actually have to call start() on our animation builder, as the animations automatically start simultaneously once we stop declaring animations. When this is the case, the animations will not start until the next update from the UI toolkit event queue.
Note: For backwards compatibility, you can use the ViewCompat class to implement the ViewPropertyAnimator from Android API version 4 and up.
Similar to the ViewPropertyAnimator, the ObjectAnimator allows us to perform animations on various properties of the target view (both in code and XML resource files). However, there a couple of differences:
- The ObjectAnimator only allows animations on a single property per instance e.g. Scale X followed by Scale Y.
- However, it allows animations on a custom Property e.g. A view’s foreground colour.
Using a custom Property to animate the scaling of a view and change its foreground colour, we can achieve this:
- view – The view to apply the animation on
- property – The property to animate
- start color – The color the animated view starts with
- target color – The color the view should animate to
We then set the evaluator (we use an ArgbEvaluator as we’re animating between color values), set the delay and start() the animation.
Next we need to animate the scaling of the view, in which we take a similar approach. The main differences are that:
- We’re creating an ObjectAnimator instance using ObjectAnimator.ofFloat() because we’re not animating int values when dealing with the size of views
- Instead of a custom property, we use both the View.SCALE_X and View.SCALE_Y view properties
Finally, we need to animate our resized view off the screen. In this case, we use a AdapterViewFlipper to contain our views that we’re animating off screen. Using this means we can call showNext() on the ViewFlipper instance and it’ll handle animating views off the screen using the animation that we’ve defined. Then, the next view is automatically animated on screen, also using an entrance animation we’ve defined.
An Interpolator can be used to define the rate of change for an animation, meaning the speed, acceleration and behaviour during animating can be altered. Several different types of interpolators available and the differences between some of them are subtle, so I suggest trying them on a device.
- Accelerate – The view gradually accelerates until the animation finishes
- Decelerate – The view gradually decelerates until the animation finishes
- Anticipate – The view begins by a slight reverse of the stated animation before animating in a standard manner
- Anticipate-Overshoot – Similar to Anticipate, but the pull-back motion that occurs during animating is slightly more exaggerated
- BounceInterpolator – The view animates a ‘bounce’ effect before coming to a finish
- LinearInterpolator – The view to animates from start-to-finish in a linear and smooth motion
- OvershootInterpolator – The view animates an exaggeration of the given value, retracting back to the desired value
The CircularReveal animation uses a clipping circle to either reveal or hide a group of UI elements. Other than helping to provide visual continuity, it’s also a pleasant and enjoyable interaction to help gain user engagement.
As shown above, we begin by using a ViewPropertyAnimator to hide the Floating Action Button before beginning the reveal animation on our views. Setting up our circular reveal only requires a few attributes to be defined:
- startView – The view that the CircularReveal will begin from (ie the pressed view)
- centerX – The center co-ordinate for the X-axis of the clicked view
- centerY – The center co-ordinate for the Y-axis of the clicked view
- targetView – The view to be revealed
- finalRadius – The radius of the clipping circle, equal to the hypotenuse of our centerX and centerY values
Customising the transitions used to navigate between activities allows you to produce stronger visual connections between application states. By default we can customise the following transitions:
- enter – Determines how an activity’s views enter the scene
- exit – Determines how an activity’s views exit the scene
- reenter – Determines how an activity reenters after previously exiting
- shared elements – Determines how shared views transition between activities
As of API level 21, there were several new transitions introduced:
The explode transition allows views to exit from all sides of the screen, creating an explode effect from the pressed view.
This effect is simple to implement – to begin with, you need to create the following transition in the res/transition directory.
All we’ve done here is:
- Declare the explode transition
- Set the duration to 300 milliseconds
Next we need to set this as the activity’s transition. We do this by either adding it to our activity theme:
The slide transition allows you to slide in / out activities from either the right or bottom of the screen. Although you could previously achieve similar, this new transition is much more flexible.
This transition is likely to be common when switching activities, I’m particularly fond of the right slide due to its fluid feel. Again, this is easy to create:
- Declared the slide transition
- Set the transition’s slideEdge to end (right) so slides perform from the right – a bottom slide would be set to bottom
The fade transition allows you to transition activities either in or out using a fading effect.
To create it is even simpler than the previous transitions:
- Declared the fade transition
- Set the duration to 300 milliseconds
Whilst experimenting I found a couple of approaches that can help improve the transition effects stated above.
Allowing window content transitions – You’ll need to enable the following attribute in themes that inherit from a material theme:
Enabling / Disabling Transition Overlaps – When transitioning, there can be a delay when an activity is waiting for another to finish it’s transition before it can start it’s own. Depending on the use case, transitions tend to feel more fluid and natural if you enable these attributes:
<item name="android:windowAllowEnterTransitionOverlap">true</item> <item name="android:windowAllowReturnTransitionOverlap">true</item>
Excluding Views from Transitions – Sometimes we may not want to transition all of our activity’s views. I found that in most cases, the status bar and toolbar caused transition glitches. Luckily, we can exclude specific views from being included in our transition:
Toolbar and ActionBar – When transitioning between an activity using an ActionBar to one using a toolbar (and vice versa), sometimes I found that the transition wasn’t always smooth. To fix this I ensured that the two activities involved in the transition were using the same component.
Transition Duration – You don’t want to keep the user waiting too long, but you also don’t want components appearing at the speed of light. It depends on the transition that you’re using so it’s best to experiment, but I’ve found that a duration of 200–500 milliseconds works in most cases.
Shared Element Transitions
Shared Element Transitions allow us to animate the transitions between shared views across activities, this creates more pleasurable transitions and gives the user a better sense of their journey.
In our layouts, we must link any shared views with the use of the transitionName attribute — this states the transitional relationship between the views. Below shows the shared views from the animation above:
To transition between these two we begin by declaring the shared transition name, done by using the transitionName attribute in our XML layouts.
Once that’s done, we create a Pair object in activity 1) containing our transitioning view and it’s transitionName. We then pass this to our activity options instance (ActivityOptionsCompat), so both activities are aware of the shared components. From there we start our activity, passing our activity options instance:
So that’s the transition between those two views, what about the views in the second activity that slide in from the bottom?
(Those are the guys on the left)
I’m glad you asked! This is also straightforward to achieve, as below:
As you can see, we’re creating a new instance of the Slide transition, adding the target views for the transition and setting the slide as the activity’s entry transition.
We also have the ability to create our own custom transitions using any of the animation APIs we’ve looked at so far. For example, we can take Shared Element Transitions one step further to morph transitioning views — this can come in useful when we wish to display dialogs (or similar pop-up views), as shown below:
Let’s take a quick look at what’s happening here:
- We begin by creating a SharedTransition, passing in the pressed view along with the transition name to reference the shared component
- Next we create an ArcMotion instance, this allows us to create a curved motion effect when transitioning between two views
- Then we extend ChangeBounds to create a custom transition to morph the two shapes (we have separate class for the button and FAB). Here we override the various methods from the class so that we can animate the required properties. We make use of the ViewPropertyAnimator to animate in the transparency of the dialogs views, the ObjectAnimator to animate between the two views colours and an AnimatorSet instance so that we can animate both of those effects together.
Animated Vector Drawables
But how do we do this? Well, let’s take a look at this one:
Made up of several different files, we begin by creating our two individual vector files which each have several properties:
- Height & Width – The actual size of the vector image
- Viewport Height & Width – Declares the size of the virtual canvas which the vector paths are drawn on
- Group name – Declare a group in which the path belongs to
- Pivot X & Y – Declare the pivot used for the group scale and rotation
- Path Fill Color – The fill color of the vector’s path
- Path Data – Declare the vector path data used to draw the vector
Note: All referenced properties are stored in a general strings file, which helps to keep things nice and tidy.
Next we declare the Animated Vector Drawable files which state both the Vector Drawable and the animations used for each drawable state (Add or Remove). Looking at the Add to Remove Animated Vector, we declare a target to:
- Animate from one state to another
- Animate the rotation of the drawable
We then need to create each of the files referenced in those targets.
Changing drawable state
In our add_to_remove.xml we use an ObjectAnimator to morph between the shapes using the following properties:
- propertyName – The property to animate
- valueFrom – The starting value for the vector path
- valueTo – The target value for the vector path
- duration – The duration of the animation
- interpolator – The interpolator used for the animation
- valueType – The value type we’re animating
Rotating the shape
We take similar approach to rotate the shape, using the rotation property and values instead:
Animating the opposite (from Remove to Add) works the same, just with the animation values reversed.
Whilst only skimming the surface, I hope this article has provided an insight into how you can create meaningful motion in your applications. I’m looking forward to learning how I can push them further and improve the way my projects look and feel.
If you enjoyed this article, then please hit the recommend button!
I’d love to hear your thoughts and where you’re using these animations – please leave a response or drop me a tweet!
Here are some resources you may find helpful on this topic:
(A big thanks to the authors!)