Discover an Easy Way to Create Complex Animations with MotionLayout
Animations are important. iOS developers seem to know that because applications in the App Store are usually much more polished than their Android counterparts.
What’s the reason behind this? Are Android devs simply lazy?
The problem is that for a long time Android SDK didn’t offer great tools for creating animations. This has been changing throughout the years and nowadays creating beautiful animations in Android is a lot easier.
At the I/O 2018 conference, Google introduced yet another great library — MotionLayout. In this article, we will take a deep dive into the world of Motion Layout and explore countless possibilities that it offers to developers.
MotionLayout — An Easy Way to Create Complex Animations
So, what’s the MotionLayout? Simply put, MotionLayout is a Viewgroup that extends ConstraintLayout.
We can define and constraint children just like we would do when using standard ConstraintLayout. The difference is that MotionLayout builds upon its capabilities - we can now describe layout transitions and animate view properties changes.
The amazing thing about MotionLayout is that it’s fully declarative. All transitions and animations might be described purely in xml.
Before we start playing with MotionLayout, we need to add suitable dependencies to a project.
The most important parameter here is app:layoutDescription. It lets us point to a scene definition. In this scene, we will define our layout transitions.
Another interesting parameter is app:motionDebug=”SHOW_ALL” Thanks to this parameter, our layout will show information helpful with debugging and adjusting animations, namely the path and progress of the animation.
Now we need to define our scene. Take a look at the diagram below.
As you can see, MotionScene consists of two major blocks.
Transition block contains several pieces of information:
The touch handler defines the way users will interact with the animation. The animation might be started with a click action or a user might progress animation with a swipe gesture.
KeyFrameSet will enable us to fully customize animations. We will take a detailed look at keyframes later in this article.
References to starting and final layout constraints.
Apart from that, we need to define a starting and final constraint set. We don’t need to define constraints for views that are still during the animation.
Now that we know the basics we can create our scene.
motion:constraintEndStart — references final layout constraints
motion:duration — defines the duration of the animation. Note that it has no effect if the touch handler is defined as OnSwipe. In that case, the duration of the animation is defined by the velocity of the user's gesture and additional parameters that will be described later.
motion:dragDirection — determines the direction of the gesture that needs to be performed to progress the animation. If it’s equal to “dragRight,” a user needs to swipe from left to right to progress the animation. If a user swipes from right to left, the animation will be reversed.
motion:touchRegionId — defines the view that needs to be dragged to progress the animation. If it’s not defined, a user might swipe anywhere within MotionLayout.
motion:touchAnchorId — parameter might be a little confusing. We need to tell MotionLayout how much the animation should progress given the number of pixels a user dragged their finger on. So the library will determine how much a user's swipe gesture progresses the animation based on the distance between the starting and final position of the touchAnchor view.
motion:touchAnchorSide — determines the side of the touchAnchor view.
motion:dragScale — determines how much a swipe gesture will progress the animation. If dragScale is equal to 2 and a user's finger moves 2cm, the touch anchor view will move 4cm.
motion:maxAcceleration — determines how fast the animation will snap to initial or final state once the user releases their finger.
motion:maxVelocity — is similar to motion:maxAcceleration but determines the maximum velocity.
<ConstraintSet> — is a set of initial or final layout constraints. Each constraint defines attributes for a particular view. Take note that we can’t declare any view attribute as a part of a constraint. It should describe view position or one of the following:
If we need to animate a different view attribute, we should declare it as <CustomAttribute>. We will learn how to do this in the next section.
Let’s take a look at the animation we created.
As you can probably remember we enabled debug overlay.
Thanks to this we can see text at the bottom describing the animation progress and animation frame rate. We can also see paths of our animated views.
The blue rectangle is the anchor view.
Custom Attributes for MotionLayout
MotionLayout also allows us to animate custom attributes. Let’s take a look at how to do this.
This scene is very similar to the last one. We’ve added <CustomAttribute> to our constraints. Circle radius will be changing its value from 0 to 1 while the animation progresses.
To create a custom attribute, you need to define a variable with public getter and setter in a custom view and then pass its name as motion:attributeName. <CustomAttribute> can be also used for already defined view properties like backgroundColor.
Thanks to custom attributes, circle size and background color are animated.
Material design guidelines often recommend using arc motion in animations as it’s more natural. How can we achieve it with MotionLayout?
One way would be to use KeyFrameSet. We will learn about this option later. The second way is to use the pathMotionArc parameter.
motion:framePosition determines when the position should be reached. It’s an integer in range [0,100]. For example value 50 would mean that position will be reached in the middle of the animation.
motion:keyPositionType — determines how exactly motion:motionPercentX and motion:percentY will affect view’s position. Possible values are:
deltaRelative — means that the values you provided will be relative to the view's animation distance on a given axis. For example, if during the animation a view moves down 100 pixels and motionPercentX is equal to -50% then it means that in the initial stage of the animation your view will move up 50 pixels and then it will move down 150 pixels to its final destination.
parentRelative — is similar to deltaRelative but the values will be relative to the parent size.
pathRelative — behaves similarly to previous attributes but changes the coordinates system. In our new coordinate system X-axis is the direction of the target view. Y-axis is perpendicular to X-axis. Thanks to this you can move your object up and down even if the starting position’s y coordinate is the same as the final position’s y coordinate. That wasn’t the case for deltaRelative.
Note that the attributes motionTarget, transitionEasing, curveFit, and framePosition are common for all keyframe types.
Let’s define the key frameset with a single KeyPosition and add it to our previous scene.
As you can see we are modifying the view’s opacity, rotation, scale, and translation on Z-axis. Let’s check out the result.
So far we have talked about the two simplest key frames. The other really interesting keyframe is KeyCycle.
Keycycles aim to solve one issue — what if we would like to repeat a motion many times during the animation? It would be very difficult with our current toolset. We would have to define many KeyPosition frames to achieve it. Keycycle frames let us define view parameter change that will be repeated in a cycle.
Let’s examine what attributes can we define for KeyCycle frames:
Standard view attributes like android:alpha, android:rotation.... See the list compiled for KeyAttribute earlier in this article.
Shared attributes for all key frames that we described in the KeyPosition section.
waveShape determines the function that will be used to generate repeating motion. Possible values are: “sin,” “cos,” “triangle,” “square,” “triangle,” “sawtooth,” “reverseSawtooth,” “cos,” “bounce.” The value of your attribute will be multiplied by adequate values returned by the chosen function.
waveOffset — describes offset applied to your attribute value. So the actual value for your attribute at time t is equal to the waveFunction(t)*initialAttributeValue + waveOffset.
wavePeriod — determines the number of waves (cycles) performed during the animation duration.
Before we start experimenting with the code let’s get familiar with a very useful tool from Google — CycleEditor. Cycle editor will let us examine in detail how our designed cycles will behave.
This tool is quite simple to use. We can configure our cycles in the top right panel. We can add, delete key cycles, and configure parameters: framePosition, wavePeriod, waveOffset, waveShape. We can also start and pause the animation.
In the bottom left panel, there’s a button that will let us preview the animation. In the right bottom panel, there’s the resulting xml file.
The left top panel is the most interesting. We can see the resulting key cycle function describing the value of the specified attribute during the animation progress.
Let’s start coding! First we will define layout xml.
We’ve defined two key cycles. One at the start of the animation and the second one at the end. Let’s see the result.
As we can see in the gif above the image is enlarged and shrank 4 times. The animation is slower at the beginning and faster at the end. It’s caused by the KeyCycle at 0 frame position.
Let’s confirm this observation using the cycle editor tool.
Indeed the first cycle is much wider than the rest.
We have 4 waves because we defined the wavePeriod as equal to 4. Also note that thanks to waveShape our wave is similar to sinus function. Its values are offset by 1.
You can combine many key cycles to get really interesting results. We recommend using the mentioned beforehand key cycle editor as it makes the job much easier.
The last key frame that we would like to mention is KeyTimeCycle. It behaves similarly to KeyCycle. The difference is that it runs indefinitely and it is independent of the animation itself. The key attribute is wavePeriod. It describes the number of cycles per second.
The KeyTimeCycle’s animation runs indefinitely in the loop.
We hope that you found MotionLayout interesting. We love using it in our projects as this relatively simple tool lets us build complex animations.
The good news is that Google still actively works on enriching the capabilities of MotionLayout. The latest release added support for rotational OnSwipe and view transitions. We are very excited to try the new features.
At nomtek, we love exploring use cases for MotionLayout. I hope you too will find a lot of use for MotionLayout in your future projects. Feel free to share your thoughts in the comment section!
Stay up to date with news on business & technology
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.