Thanks to its remarkable versatility, the ConstraintLayout
widget has become the “Swiss Army knife” of layouts for Android app developers. However, adding complex animations to its contents, although possible, can be quite time-consuming. That’s why Google introduced the MotionLayout
widget in I/O 2018.
The MotionLayout
widget, which is now a part of Android’s support library, extends the ConstraintLayout
widget. It is a unique widget that allows you to animate its contents declaratively using just XML. Furthermore, it offers fine-grained control over all its animations.
In this tutorial, I’ll show you how to add it to your Android Studio projects and create a few different animations with it.
Prerequisites
To follow this tutorial, you’ll need:
- Android Studio 3.1.3 or higher
- a device or emulator running Android API level 21 or higher
- a basic understanding of the
ConstraintLayout
widget
1. Add Dependencies
To be able to use the MotionLayout
widget in your Android Studio project, you must have the latest version of the Constraint Layout support library as an implementation
dependency. Additionally, in order to avoid version conflicts, make sure you include a dependency for the latest stable version of the v7 appcompat support library.
Accordingly, add the following code to the app
module’s build.gradle file:
implementation 'com.android.support:appcompat-v7:27.0.2' implementation 'com.android.support.constraint:constraint-layout:2.0.0-alpha1'
2. Define a Layout
The MotionLayout
widget can do everything the ConstraintLayout
widget can. Therefore, you can freely replace any instance of the latter with the former. For now, however, I suggest you create a new layout XML file and add the MotionLayout
widget to it as the root element.
Throughout this tutorial, we’ll be animating an ImageView
widget. So add it as the first child of the layout.
You’re free to use any drawable as the source of the ImageView
widget. In the above code, I’m using a color drawable.
Next, add a button you can press to start the animations. The following code shows you how to position it in the center of the layout:
Additionally, to monitor the progress of the animations, add a SeekBar
widget to the layout and position it below the button. Here’s how:
Lastly, because there’s an on-click event handler associated with the button, make sure you define it in your activity.
fun start(v: View) { // More code here }
3. Create a Motion Scene
You may have noticed that we did not add any constraints to the ImageView
widget while defining the layout. That’s because we’ll be adding them to a motion scene instead. A motion scene is an XML file that contains details about the animation you want to create with a MotionLayout
widget.
To create a new motion scene, create an XML resource file and add a MotionScene
element to it.
A motion scene contains ConstraintSet
elements specifying the constraints that must be applied to a widget at different points in the animation. Motion scene files usually contain two constraint sets: one for the beginning of the animation and one for the end.
The following code shows you how to create two constraint sets that will help the MotionLayout
widget move the ImageView
widget from the bottom right corner of the screen to the top left corner:
Note that each ConstraintSet
element must always specify both the desired position and the desired size. This is important because it will overwrite any previously set layout information.
To help the MotionLayout
widget understand the order in which the constraint sets must be applied, you must next create a Transition
element. By using its intuitively named constraintSetStart
and constraintSetEnd
attributes, you can specify which set must be applied first and which last. The Transition
element also allows you to specify the duration of the animation.
At this point, the motion scene is complete. However, the MotionLayout
widget is still not aware of it. So go back to the layout XML file, add a layoutDescription
attribute to the widget, and set its value to the name of the motion scene file.
If the name of your motion scene file is my_scene.xml, your MotionLayout
widget should now look like this:
...
4. Start the Animation
When you run the app, the MotionLayout
widget will automatically apply the constraint set specified in the constraintSetStart
attribute of the Transition
element. Therefore, to start the animation, all you need to do is call the transitionToEnd()
method of the widget. The following code, which must be added to the on-click event handler you created in an earlier step, shows you how:
motion_container.transitionToEnd()
At this point, if you run the app and press the button, you should be able to see the ImageView
widget smoothly move across the screen.
5. Handle Animation Events
By attaching a TransitionListener
object to the MotionLayout
widget, you can closely monitor the progress of the animation.
motion_container.setTransitionListener( object: MotionLayout.TransitionListener { // More code here } )
The TransitionListener
interface has two abstract methods, and Android Studio will automatically generate stubs for them.
The onTransitionCompleted()
method is called when a transition from one constraint set to another is complete. For now, let’s use it to reset the constraints of the ImageView
widget by calling the transitionToStart()
method inside it.
override fun onTransitionCompleted(motionLayout: MotionLayout?, currentId: Int) { if(currentId == R.id.ending_set) { // Return to original constraint set motion_container.transitionToStart() } }
The onTransitionChange()
method is called every time the progress of the animation changes. As such, the progress is a floating-point number that lies between zero and one. The following code shows you how to update the SeekBar
based on the progress of the animation:
override fun onTransitionChange(motionLayout: MotionLayout?, startId: Int, endId: Int, progress: Float) { seekbar.progress = ceil(progress * 100).toInt() }
Go ahead and run the app again to see two animations now.
6. Create Key Frames
In our animation, the ImageView
widget moves in a path that looks like a straight line. That’s because the MotionLayout
widget is given only two points to work with: the starting point, which is at the bottom right corner of the screen, and the ending point, which is at the top left corner of the screen. If you want to alter the shape of the path, you’ll have to provide a few intermediate points, which lie between the starting and ending points. To do so, you’ll have to create new key frames.
Before you start creating key frames though, you must add a KeyFrameSet
element to the Transition
element of your motion scene. Inside the new element, you are free to create any number of key frames.
The MotionLayout
widget supports many different types of key frames. In this tutorial, we’ll be working with only two types: KeyPosition
frames and KeyCycle
frames.
KeyPosition
frames are the ones that help you alter the shape of the path. While creating them, make sure you provide the ID of the target widget, a position along the timeline, which can be any number between 0 and 100, and the desired X or Y coordinates specified as a percentage. The coordinates can either be relative to the actual X or Y axes or be relative to the path itself.
The following code shows you how to create two key frames that force the ImageView
widget to follow a path that avoids collisions with the button and the seek bar:
If you run the app now, you should see an animation that looks like this:
You are, of course, free to add more key frames. For example, by adding the following key frame towards the end of the timeline, you can make the ImageView
widget follow a more wavy path:
By using a KeyCycle
frame along with the KeyPosition
frames, you can add oscillations to the animation. While creating it, you must again provide the ID of the target widget, a position along the timeline, and the desired value of the property that has to oscillate back and forth. Additionally, you must configure an oscillator by providing details such as the wave shape to use and the wave period.
The following code creates a KeyCycle
frame that uses a sine-wave oscillator to periodically rotate the ImageView
widget by 50 degrees:
On running the app again, you should see an animation that looks like this:
7. Make Animated Widgets Interactive
All this while, you’ve been pressing a button to start the animation. Such a button, however, is not always necessary because the MotionLayout
widget allows you to directly attach touch event handlers to the widgets being animated. Currently, it supports the on-click and the on-swipe events.
For instance, you can add the following OnClick
element, which targets the ImageView
widget, inside the Transition
element of your motion scene to make the button redundant:
Similarly, you can use an OnSwipe
element to allow the user to manually drag the ImageView
widget across the screen. While creating the element, you must make sure you provide the right drag direction and the side of the widget that should act as the drag handle.
If you run the app again, you should now be able to drag the ImageView
widget.
Conclusion
You now know how to use the MotionLayout
widget to quickly add complex, interactive animations to your Android apps. You can be sure that the animations will run without any lag or jitter on most devices, so long as you avoid nested views.
It’s worth noting that upcoming releases of Android Studio will include a visual Motion Editor, which is likely to further improve the usability of the widget.
To learn more, do refer to the official documentation.
Powered by WPeMatico