Swipe right on Motion Layout
Tags: Android, Animation, Motionlayout, Programming, Kotlin

Stack of cards

Showing a swipeable card

We will start by defining a Motion Layout in our layout directory containing the single top card for now.

Pay attention to the following line: app:motionDebug=”SHOW_ALL”

It allows us to draw debug information on the screen along with the paths of the motions, the state containing our current start and end animation, and our progress. It comes in really handy when debugging, but don’t forget to remove it before it hits production as there is no reminder to do so.

You can see that we don’t define any constraint on the views in this layout. They will be taken from the layout scene which we will be now defining.

We will start by defining our initial, rested state which consists of a single card in the middle of the screen with added margins on the sides.

Let’s add the pass and like constraint set. They will represent the state of the top card when fully swiped to the left or the right. We want the card to stop before going out of screen, to then, have another nice automated animation to confirm the decision.

We add both constraint sets to our previous Motion Scene. They are almost the same but differ only in the fact that they mirror each other on each side of the screen.

We have three ConstraintSet defining the start, the like and pass state. We now need to define how we want to transition between these states.

To do so, we add transitions to our Motion Scene, one to swipe left and one to swipe right.

We specify both animation, one on our top card, on swiping left on the left side of the card, and mirrored for the other side.

Some arguments that makes our animations react nicely:

touchRegionId: since we are adding margin on the side of our card, we only want the top card area to intercept touch and not the whole Motion Layout. Adding this flag lets us do so.

onTouchUp: what will happen to the animation after we release the card. We want the card to either continue animating or settle back, so we use autoComplete.

Let’s see how this goes:

Make the card automatically fly off the screen

Let’s work on the animation triggered after the state we defined before. It will effectively make the card disappear by flying off the screen.

We are going to add two more ConstraintSet as before, for each end state of our animations: one off the screen to the left and one to the right.

In the next examples I will only show how to do the like state, the pass one is always going to be the mirrored state. Please refer to this repository to see the full working example.

We now, like before, need to define the transition to go from our swiped state to this end state. In this case, we want it to trigger automatically as soon as the swipe animation is done. We can do this by specifying an autoTransition on our transitions like this:

We now have one swipeable card that flies off the screen!

Animate the bottom card

Let’s now define our bottom card to create the illusion that the stack is infinite.

We define another card in our layout, similarly to the first one:

We changed our xml to specify the constraints applied to this card for each stage of our animation:

We can take advantage of a neat feature of the Constraint Set to do so.

By default each new constraint set take the attributes, if not otherwise specified, from the parent Motion Layout. We can change this behaviour by defining a parent to our constraint set with the flag deriveConstraintsFrom. One thing to keep in mind is that if we define our constraints in the constraint tag, we override every constraint in the derived set. To avoid this we can define specific attributes in defined tags that will only override the attributes affected by the tag.

In our example, it means that in the pass constraint set we don’t define the Layout tag so we copy it from our parents. But we do override Transform, so we dismiss every attribute defined in the Transform tag with our own, in this case, a change of scale.

And that’s how easy it is with Motion Layout to add a new element and seamlessly integrate it into our animation flow.

Make it infinite

After the animation, our top card is not swipeable anymore since it’s now the bottom card that is taking its place. We need to swap the cards over in order to achieve an infinite animation.

My first instinct was to do this with a new transition like so:

It looks like it’s working as expected with the animation executing right through to the end.

We have now an infinite swipeable deck of cards!

By playing a bit with it, I realized something. The jump-to-end animation freezes if we touch the card. Even if nothing can be done, and the animation duration is zero seconds, we still end up freezing it, and that’s not good.

The only way I got it to work as we wanted was to change instantly the active transition on the Motion Layout, programmatically.

For this, we define a listener that has a callback when the animation is finished. Once both offScreenLike and offScreenPass finishes we just reset the transition back to rest and reset the progress to zero.

It doesn’t matter which transition we define, pass or like, when swiping we will switch to the correct one.

It leaves us with the same result as before, avoiding the animation stopping! Onto the next bit!

Bind the cards

Let’s create some dummy data to show on the cards. We will just add a different background colour for each card for now.

We create a ViewModel with a swipe method that will just shift the data. We bind it in our Activity like below:

We just have to let our ViewModel know when the swipe animation is finished and it will update the data being shown at that moment.

Popout views

Let’s add the two views that pop out from each side when we swipe (only showing one, the other is mirrored).

We create both views in our layout file and for the cards we need to define the animation states for them.

We don’t have to define the constraints in the animations that fly off the screen since they will maintain the same states as the set they derived from, which in this case will be swiping.

That’s all we need to do. Now adding components to our animation chains is really easy.

Triggering animation programmatically

We can define two buttons on our cards that the user could directly click on to interact with the cards along with the swiping mechanic.

Either of these buttons will trigger the same animation that would be otherwise triggered by the swiping gesture.

We register our click listener in our Activity as usual and trigger the animation directly on the Motion Layout:

We need to define both buttons on our top and bottom card so that the animation runs continuously. There is no need to define any listener on our bottom card though, because if they are being clicked, the first card is still animating.

It’s also another great example of how motion layout handles the changes of state for us. Let’s slow down the animation a bit to check this out more closely.

You can see the transition the Motion Layout makes when going from the like directly to the pass state. Pretty cool!

Curve the card path

Let’s say that curving the path of the card would be nicer. (to be honest, I just wanted to give it a try)

To do that we need to define a KeyPosition in our motion for both sides of the card so as to ‘bend’ the path of the card into an arc shape.

By adding it to our motion scene:

We get this nice curvature of the card path. Pretty neat!