Building a Scrum Poker App using MotionLayout

Matias Isella
The Glovo Tech Blog
6 min readMay 28, 2020

I think animations are one of the most complicated things to do in Android. Making a simple animation sometimes takes a lot of code, and it gets harder if you need to synchronise them.

Luckily, Android provides a few tools that enhance the user experience. One of the latest ones is MotionLayout introduced in the Google I/O 2018. Even though it is still under development, the tool appears to be stable on beta 6.

So, I have decided to give it a go by writing a simple Scrum Poker application.

In case you don’t know what Scrum Poker is, here you can find the rules.

Summary

Basically the app will display 3 states:

  • Initial: Showing all the cards
  • Selected: Showing the selected card flipped down
  • Revealed: The card is shown

The goal is to develop the app using as few lines of code as possible relying on the MotionLayout XML description file to build and trigger the animations for us.

The finished project can be found on Github.

Let’s do it

If you plan to follow this post step by step, I encourage you to use Android Studio 4 (Currently on Preview) because a few design tools have been included. The Motion Editor, for example, allows you to navigate through the sequences, and help you preview what the layout will look like.

Motion Editor available on Android Studio 4

To start with, you need to use an Empty Activity project from the Android Studio Project Templates.

Once you have set up your Project, you just need to add the dependency in gradle.properties and you are good to go.

implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta6'

Let’s set up a simple RecyclerView with an Adapter to show the cards.

Now you might notice something strange, you are leaving the TextView selected_card_title outside the Card. What is this for?

MotionLayout inherits from ConstraintLayout, which works with a flat hierarchy view (there are no nested view groups). So if you want to animate or change the constraint of that view, you have to do it at root level.

Let’s use data binding for the cards, to reduce the boilerplate, and focus on the MotionLayout.

Now, let’s define the Domain Class and the Adapter to display these Cards.

Once the initial setup is done, you are ready to build your Scene.

A MotionScene is an XML resource file that contains all of the motion descriptions for the corresponding layout.

Source: https://developer.android.com/training/constraint-layout/motionlayout

Initial Sequence

This App will be used by tapping on the cards. Each of these taps leads to the next step until the last step returns to the initial one.

These “states” are built in the MotionScene using sequences (ConstraintSet) and Transition.

A flat example of what you will build:

ConstraintSet

Specifies the positions and attributes of all of the views at one point in a motion sequence. This is done through the only possible child this Tag which is:

<Constraint>

Here you need to set the id of the view that you want to apply the changes to. Then, set of the properties to put the constraints.

This can be done by setting them on the parent tag or using a nested <Layout> tag with the constraints, which is the recommended way to do it.

❌ Don’t

<Constraint android:id="@id/cards"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

✅ Do

<Constraint android:id="@id/cards">
<Layout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</Constraint>

Why? This is important in order to use the property app:deriveConstraintsFrom, which allows you to “inherit” the layout from the previous motion sequence, and reduce the number of lines needed to achieve the same results.

Transition

Specifies the beginning and end state of the motion sequence.

These elements are the drivers of the scene. Later on, you will see that these transitions can be triggered by user actions like clicks, but also can alter the sequence to synchronize the changes and produce a smoother transition.

The final result for the Initial Sequence

<ConstraintSet android:id="@+id/initial">

<Constraint android:id="@id/cards"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<Constraint android:id="@id/selected_card">
<Layout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="38dp"
android:layout_marginTop="38dp"
android:layout_marginEnd="38dp"
android:layout_marginBottom="38dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="2.5:3.5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<PropertySet android:visibility="gone" />
<CustomAttribute
app:attributeName="cardBackgroundColor"
app:customColorValue="@color/cardBackColor" />
</Constraint>

<Constraint android:id="@id/selected_card_title">
<Layout
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="@id/selected_card"
app:layout_constraintEnd_toEndOf="@id/selected_card"
app:layout_constraintStart_toStartOf="@id/selected_card"
app:layout_constraintTop_toTopOf="@id/selected_card" />
<PropertySet android:visibility="gone" />

</Constraint>
</ConstraintSet>

Selected Sequence

Now it is time to change the state when the user taps on one of the cards. But how is this new sequence supposed to look like? Let’s see… you want to:

  1. Hide the cards.
  2. Display the selected card, but we want to show it flipped.
  3. Hide the selected card number.

For this, you need to work a bit more. And as you can see depending on the changes you want to perform, the tag you need to use:

  • For visibility (and alpha) you will need to use <PropertySet>.
  • For transformations included in the Android Animation API, you will need to use <Transformation>.
  • And for properties that are not in the Animation API. You will need to use <CustomAttribute> which uses reflection to update the property defined. This is quite useful when animating custom views, or in this case a material card.
<ConstraintSet
android:id="@+id/selected"
app:deriveConstraintsFrom="@id/initial">

<Constraint android:id="@id/cards">
<PropertySet android:alpha="0" />
</Constraint>

<Constraint android:id="@id/selected_card">
<PropertySet android:alpha="1" />
<Transform android:rotationY="180" />
<CustomAttribute
app:attributeName="cardBackgroundColor"
app:customColorValue="@color/cardBackColor" />
</Constraint>

<Constraint android:id="@id/selected_card_title">
<Transform android:rotationY="180" />
</Constraint>
</ConstraintSet>
<Transition
android:id="@+id/select_card"
app:constraintSetEnd="@id/selected"
app:constraintSetStart="@id/initial" />

At this point if you have Android Studio 4 running, you can use the Motion Editor to see the transition between the two sequences:

Improving the Transition

As you can see in the editor, the animation is a bit overwhelming, the cards are fading out while the “selected” card is spinning from the background.

So here are the KeyFrames to help you achieve a more organized transition.

<Transition
android:id="@+id/select_card"
app:constraintSetEnd="@id/selected"
app:constraintSetStart="@id/initial">

<KeyFrameSet>
<KeyAttribute
android:alpha="0"
app:framePosition="30"
app:motionTarget="@id/cards" />

<KeyAttribute
android:rotationY="0"
app:framePosition="30"
app:motionTarget="@id/selected_card" />
</KeyFrameSet>
</Transition>

What we are doing is rushing the alpha of the cards to 0 at 30% of the sequence. And then prevent the card from spinning until 30% of the sequence.

Final Sequence

I think we are ready to reveal the final MotionLayout.

The last two transitions are quite similar to the first one, but I am adding an extra element to them:

<Transition
android:id="@+id/reveal_card"
app:constraintSetEnd="@id/revealed"
app:constraintSetStart="@id/selected">

<OnClick
app:clickAction="transitionToEnd"
app:targetId="@id/selected_card" />
</Transition>
<Transition
android:id="@+id/restart"
app:constraintSetEnd="@id/initial"
app:constraintSetStart="@id/revealed">

<OnClick
app:clickAction="transitionToEnd"
app:targetId="@id/selected_card" />
</Transition>

The <OnClick>. This transition allows you to define a clickAction to the element. This is quite useful in our case, since we want to move to the next sequence onClick.

You probably want to enhance the transitions, in the same way we did it with the first one.

Synchronizing

I have added a KeyFrameSets at 50% in order to synchronize the appearing of the number at the same time the card is rotating.

    <KeyFrameSet>
<KeyAttribute
android:alpha="0"
app:framePosition="50"
app:motionTarget="@id/selected_card_title" />
</KeyFrameSet>
Number synchronized with the card in slow motion

Summarizing

We were aiming to build this application using as few lines of Kotlin code as possible relying on what MotionLayout has to offer, a simple description file that is human readable.

In the end, it is easier to maintain a XML file than synchronize animations callbacks in your code.

--

--