Defining motion paths in MotionLayout
By: Nicolas Roard
Tags: Motion Design, Android, Motion, Android Studio, Constraintlayout

Position Keyframes

The position keyframes are likely going to be the most common keyframes you will encounter or use. They allow you to modify the path a widget will take on screen during a transition. For example, let’s take the following animation of a single widget, contained in a MotionLayout (“parent”):

We have a start (bottom left) and end (top right) states, and the motion path is simply the linear interpolation between those two states — the widget will move in a straight line.

By introducing a position keyframe, we can change the motion path to a curved motion:

Adding more keyframes will allow you to create complex motion paths.

<KeyFrameSet>

<KeyPosition

motion:keyPositionType="pathRelative"

motion:percentX="0.75"

motion:percentY="-0.3"

motion:framePosition="25"

motion:target="@id/button"/>

<KeyPosition

motion:keyPositionType="pathRelative"

motion:percentY="-0.4"

motion:framePosition="50"

motion:target="@id/button"/>

<KeyPosition

motion:keyPositionType="pathRelative"

motion:percentX="0.25"

motion:percentY="-0.3"

motion:framePosition="75"

motion:target="@id/button"/>

</KeyFrameSet>

Why Position Keyframes?

You may ask yourself what is the point of position keyframes, if ConstraintSets already allow you to position your widget in a very flexible manner. There are a few reasons:

keyframes are expressing a transient modification, while ConstraintSets express a “resting” state

keyframes are more lightweight than ConstraintSet to compute

position keyframes allow you to manipulate the motion path of a widget— ConstraintSets instead specify the position of a widget, relative to other widgets.

Note: It is possible to define multiple ConstraintSets within a MotionScene, so if you have a multi-step motion where such steps are valid “resting” state, you can use them instead of keyframes. Transitioning state to state would have to be done in code (change listeners are available).

XML Representation

Keyframes are contained in a <KeyFrameSet> attribute, itself contained in a <Transition> in the MotionScene file. Position keyframes are represented via the tag <KeyPosition> , and need to contain at least:

target : the widget the keyframe apply to

: the widget the keyframe apply to framePosition : from 0 to 100, when does the keyframe applies

: from 0 to 100, when does the keyframe applies keyPositionType : the coordinate system used, parentRelative, deltaRelative, pathRelative

: the coordinate system used, percentX / percentY : the (x,y) coordinate of the position

<Transition ...>

<KeyFrameSet>

<KeyPosition

motion:keyPositionType="parentRelative"

motion:percentY="0.25"

motion:framePosition="50"

motion:target="@+id/button"/>

</KeyFrameSet>

</Transition>

Different coordinate systems

The start and end states in MotionLayout allow complex positioning. As ConstraintSets, they have access to the full capabilities of ConstraintLayout. The system will correctly handle changes in density, screen orientation, language, etc., for those states.

For position keyframes to be useful in such a system, we need them to be able to position themselves in a similar, adaptive manner — we can’t simply have them be defined as fixed positions.

To address this, yet keep the keyframes system lightweight, we came up with a flexible approach — each keyframe’s position is expressed in terms of a (x,y) coordinate pair, in a given coordinate system:

motion:percentX=”<float>”

motion:percentY=”<float>”

The meaning of those coordinates depends on the type of coordinate system used: parentRelative , deltaRelative , or pathRelative .

Note: each keyframe position is done individually — each one can be expressed using their own coordinate system, independent from the others.

parentRelative

The coordinates are expressed relative to the parent container. This is a very straightforward and intuitive way to express the position of the keyframe, and will often be enough. You typically would use this for large motions that need to be relative to the container.

As this coordinate system is based only on the parent dimensions, and not on the start/end positions of the moving widget, you may encounter situations where the resulting keyframe position ends in a suboptimal position (relative to the start/end positions).

deltaRelative

This second coordinate system addresses this exact issue, by being defined using the start/end positions. The coordinates express a percentage of the distance between the start and end positions.

Similar to parentRelative , this is a relatively intuitive coordinate system, and will generally give good results as well. It is particularly useful when you want widgets to begin or end in a horizontal or vertical motion.

There’s also a potential issue with it — as it’s defined based on the difference between the start and end position of the widget, if the difference is very small (or nil), the position of the keyframe will not change in the affected axis. For example, if a widget moves from left to right on the screen, while staying at the same height, using a deltaRelative percentY for a position keyframe will have no effect.

pathRelative