Magazine

Introduction to MotionLayout on Android

Want to make something move and groove in an Android app? Here is a quick how-to, sample animation included.

17 motion layout

In this article, we are going to go through some basic information about MotionLayout, a subclass of ConstraintLayout. It’s used by Android developers to create motion and widget animations in their apps. We plan to describe the building blocks needed to create those animations. Using an example, we’re going to highlight the most important steps. In the end, useful links will be listed, along with more information and examples.

Introduction

As stated in the documentation:

MotionLayout is a layout type that helps you manage motion and widget animation in your app. It is a subclass of ConstraintLayout and builds upon its rich layout capabilities.

So, it’s really powerful, it has a lot of potential, but it only works with its direct children. It does not support nested layout hierarchies or activity transitions and this should be taken into consideration.

Some of MotionLayouts key features are: describing transitions between layouts, animating layout properties, and supporting seekable transitions and keyframes for customized transitions. Lastly, it is fully declarative and any of its transitions can be described in an XML file.

Since MotionLayout is an extension of ConstraintLayout, it inherits all of its features and you need to add the following dependency to your app’s build.gradle:

dependencies {
    implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
}

Or this if you’re not using AndroidX:

dependencies {
    implementation 'com.android.support.constraint:constraint-layout:2.0.0-beta4'
}

If you add MotionLayout to your layout file, you’ll notice that Android Studio (I’m using Android Studio 4.0 Beta 3) complains with an error such as this:

Here, you can click the Generate MotionScene file button and a motion scene file will be generated for you. I named my layout file motion_layout.xml and the generated file is named motion_layout_scene.xml (it can be found inside the res/xml folder). The layout file now looks like this:

The generated motion_layout_scene.xml looks like this:

MotionScene is the root element of a motion scene file and it’s used to create animations in a declarative way. The same MotionScene file can be reused and applied to different layouts.

The ConstraintSet element has definitions for each View you want to animate in a motion sequence.

Constraint element specifies the attributes and location of a single View in a motion sequence.

Transition contains at least the start and end ConstraintSet and it describes the changes from the start to the end state. It can contain OnClick, OnSwipe and KeyFrameSet elements.

More info about mentioned elements can be found in the official documentation.

But, enough theory, let’s check out our example!

Sample Animation

Feel free to check out the Github repo with the source code below.

MotionLayoutSample

Sample animation for using MotionLayout on Android.

Kotlin

Our layout file activity_music_band_list.xml has the following hierarchy:

Here, you can see a parent MotionLayout with two children – RecyclerView and another MotionLayout, containing views that will be animated. The purpose of the parent MotionLayout is to control the visibility of its children through a motion scene file.

Before we dive deeper into the animation implementation, I wanted to mention that this is a multi-step animation. MotionLayout currently doesn’t have an API for controlled multi-step transitions. Since I have 4 ConstraintSets and need to handle 3 transitions, I have to start some transitions programmatically. For more complex transitions, with more steps, you can see an article written by Chris Banes.

When the RecyclerView item is clicked, we call the animate method that is defined in MusicBandListActivity.

The vhValues parameter is an interface with two methods that provide information necessary for positioning views by using ConstraintSet.

The musicBandModel represents a POJO of the clicked RecyclerView item.

First, we need to modify the margins of our views with values from the interface parameter:

private fun animate(
    vhValues: MusicBandAdapter.ViewHolderValues,
    musicBandModel: MusicBandModel) {

    val marginTop = 
        (vhValues.getThumbnailHeight() * SCALE).toInt() - (iconSize * SCALE / 2).toInt() - margin

    animatedView.also {
        var set = it.getConstraintSet(firstSet)
        set.setMargin(R.id.thumbnail, ConstraintSet.TOP, vhValues.getY())
        set.setVisibility(R.id.thumbnail, ConstraintSet.VISIBLE)
        set.setMargin(R.id.more_info, ConstraintSet.TOP, marginTop)
        set.applyTo(it)

        set = it.getConstraintSet(secondSet)
        set.setMargin(R.id.more_info, ConstraintSet.TOP, marginTop)
        set.applyTo(it)

        set = it.getConstraintSet(thirdSet)
        set.setMargin(R.id.more_info, ConstraintSet.TOP, marginTop)
        set.setMargin(R.id.about_container, ConstraintSet.TOP, marginTop)
        set.applyTo(it)
        // ...
    }
}

The animatedView represents the child MotionLayout, containing all the views that will be animated. The firstSet, secondSet, thirdSet are ids of the ConstraintSets.

private val firstSet = R.id.first_set
private val secondSet = R.id.second_set
private val thirdSet = R.id.third_set

They are defined in animated_music_band_item_scene.xml.

After that, we need to set data to animated views, set the transition listener, start the parent animation to hide RecyclerView, and show the child MotionLayout. Here is the code for that:

private fun animate(
    vhValues: MusicBandAdapter.ViewHolderValues,
    musicBandModel: MusicBandModel) {

    animatedView.also {
    	// ...
        with(binding) {
            thumbnail.background =
            ContextCompat.getDrawable(this@MusicBandListActivity, musicBandModel.drawableId)
            name.text = getString(musicBandModel.name)
            tags.text = getString(musicBandModel.tags)
            description.text = getString(musicBandModel.shortDescriptionStringRes)
            aboutText.text = getString(musicBandModel.aboutStringRes)

            it.setTransitionListener(transitionAdapter)

            root.transitionToEnd()
            it.setTransition(firstSet, secondSet)
            it.setTransitionDuration(ANIMATION_DURATION)
            it.transitionToState(secondSet)
            activeSet = ConstraintSetState.FIRST_TO_SECOND
        }
    }
}

The activeSet is a ConstraintSetState type which is an enum that helps us track which ConstraintSet will be next or previous.

private enum class ConstraintSetState {
    FIRST_TO_SECOND,
    SECOND_TO_THIRD,
    SECOND_TO_FIRST,
    THIRD_TO_SECOND
}

You’ll notice that the root animation is started by using the transitionToEnd() method which has only two ConstraintSetsactivity_music_band_list_scene.xml. The animation on animatedView isn’t though. That is because we have a multi-step animation and we have to set transitions manually.

Since there is quite a lot of XML code in the MotionScene file, which I do not want to copy and paste here, you can open animated_music_band_item_scene.xml and check all ConstraintSets and Transitions.

After the first transition (firstSetsecondSet), the screen looks like this:

In the first_set, we are positioning the views, setting the thumbnail dimension ratio and scale. The scale needs to be resetted because we want the initial scale while the animation is running from end to start. The actual scale is applied from frame position 80 (defined in Transition block). We are also hiding the back arrow by setting its alpha and visibility value. In the second_set, we have to set the scale type again so it would not be lost. We also need to hide other views by changing their alpha values.

When the first transition is done, we are immediately ready to start the second transition (secondSetthirdSet) in the onTransitionCompleted callback. This results in the following screen:

We’re starting from the second_set and, in the third_set, we are making the views visible while controlling the situation when the animation will be executed from the end to start state. Inside the Transition block, the OnClick action is used to toggle between second_set and third_set.

The last step of the animation is completely defined in the motion scene file because we can choose actions when the user clicks on the view. In our scenario, we again have an OnClick action inside Transition which handles the start and the end state for us. There is also a KeyFrameSet element which contains the KeyAttribute elements used to change alpha values for specific views during transition. Attribute app:framePosition can hold any value between 0 and a 100. Another useful class is ImageFilterView which, in our case, is used to animate between two images with its crossfade attribute.

The screen now looks like this:

Conclusion

This article provided some general information about MotionLayout and a sample animation with all the pieces put together. Even though it’s still in beta, that doesn’t prevent us from creating beautiful animations. If you want to find out more about MotionLayout here are some resources:

I hope that you learned something new in this article. Feel free to post any questions or comments below or contact me on denis@arsfutura.co. Cheers!

Leave a comment Be the first!