Introduction to MotionLayout
Creating a basic animation using Motion Layout.
Currently on the Android world we’re mostly chained to the Animations API , e.g:
- Animated Vector Drawable
- Property Animation framework
- LayoutTransition animations
- Layout transitions with TransitionManager
- CoordinatorLayout
I‘m going to explain what is MotionLayout and why you should consider use it instead of made all your animation in code. The MotionLayout animation system works by interpolating values (typically, position/size of widgets) between two states, specified using the full Constraint system of ConstraintLayout, as well as view attributes.
MotionLayout was created to bridge the gap between layout transitions and complex motion handling. You can think of it in terms of capabilities as a mix between the property animation framework, TransitionManager, and CoordinatorLayout.
Also don’t abuse about this new feature, the reason why we put animations in our apps is to improve the User Experience for our users, but keep in mind that adding an animation should be meaningful and not over do it with the animations
So, how we should start to use the MotionLayout animations in our Android Apps?
MotionLayout
inherits from ConstraintLayout
, this means we have all the power of constraints to define our states.
MotionLayout
calculates the difference between our layout at the beginning and at the end to create our animation e.g: we have an initial state that has an image at the bottom of the screen and a end state with the image at the top (See the screenshots).
So, how this does work with MotionLayout, how can I make the animation transition from the bottom to top and vice versa?
- Set up: Adding the dependency
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
2. Creating our first layout
A useful tip, before to add the MotionLayout tag inside our layout view we should first construct the view as a normal ConstraintLayout and after that we can make the change to MotionLayout
When you finish the layout view as a ContraintLayout and after that made the change to MotionLayout tag maybe you receive an error:
The attribute: layoutDescription is missing
To solve this issue you only need to add the layoutDescription to describe the MotionLayout scene that it’s gonna be used, e.g:
app:layoutDescription="@xml/scene_main"
After declare the scene in the layoutDescription and create all the components inside our MotionLayout view.
3. Creating our Motion Scene
All our animations are described in scene_main.xml. We don’t need to add other stuff to instance our animation or create an ObjectAnimator and so on. Next is show the scene_main layout, see the next screenshot
If you are seeing the scene_main layout and don’t understand nothing or just doesn’t have a clue of some tags it’s ok, you don’t have to be worried because we’re going a little deeper to explain every tag and their respective functions.
In the “scene_main” we are using the following tags:
- ConstraintSet
- Constraint
- Layout
- Transition
- OnClick
- KeyFrameSet
- KeyAttribute
ConstraintSet: The ConstraintSet basically is the one who is in charged of encapsulate all the positioning rules for your layout, you can declare as many ConstraintSet you want but for the post we’re gonna use just 2 that would be the ConstraintSet Start and the ConstraintSet End, you also can decide which rules to apply to your layout, e.g: In the scene_main we already create the first ConstraintSet with the @id/start and inside of the ConstraintSet we’re gonna set the initial state for the animation, inside the ConstraintSet you can declare the component inside your layout and define his sizes, rotation, alignments and so on.
The other ConstraintSet is the @id/end that has the finished state for the animation and in the scene_main we add some other widgets which going to be involved in the final state of our animation.
Constraint: The Constraint tag is the reference to your view and contain all the constraints that you want to apply to the widget, e.g: in our MotionLayout we declare an ImageView rocket (ivRocket) with his default position, now inside the scene_main we’re modifying a little bit the initial state, like we’re applying a rotation and a margin bottom and the size and alignments we’re using the same that is in the MotionLayout.
Layout: The Layout is the widget where has the constraints applied in this case the ImageView rocket.
Transition: This tag do all the magic regarding with the animation, without this, we can’t see any animation running or nothing animated, you can define the start with “contraintSetStart” and “constraintSetEnd” to do the trnasition between the 2 ConstraintSet that are already declared in the scene_main, also you can add the duration of the animation (by default is in millisenconds) and we also can specify when the animation should start. Let’s say we want to start when all the screen view is clicked.
OnClick: This tag initiate the animation through an action, currently on MotionLayout we have 2 states that are OnClick & OnSwipe
- OnSwipe can let you control the movement through the touch.
KeyFrameSet: This tag let you to manage the KeyAttributes that are gonna be modifying the animations.
KeyAttribute: This tag let you modify the widgets animations in the frame position whenever you want to add a change, e.g: let’s scale the rocket in the framePosition 0 (the imageView would be scaled at the start of the animation)
In the scene_main we add some keyAttributes to modify the satellite imageView when the animation has started & finished.
4. Running the project to see the final animation
The result animation would be this:
If you’re wondering in how the animation can go up and outside off the screen it’s because the declaration in the ConstraintSet for the end state. I add a marginBottom that’s going to be added at the end of the imageView rocket making the animation to the top and off the screen.
A MotionLayout GAP
I was trying to make a parallax effect for this example when the rocket goes up, the space background going below and a satellite is gonna show up at the right end of the top, I was thinking for a moment that this is gonna be great! But unfortunately didn’t go so well at the start because I noticed that the MotionLayout doesn’t have support for the padding in the scene layout, just the margins, so for this part I had to make a little hack to make it work as I wanted.
motionLayout.setTransitionListener(object : MotionLayout.TransitionListener {
override fun onTransitionStarted(motionLayout: MotionLayout, startId: Int, endId: Int) { }
override fun onTransitionChange(motionLayout: MotionLayout, startId: Int, endId: Int, progress: Float) {
val paddingInPx = calculatePaddingPx(progress)
bgSpace.setPadding(0, paddingInPx, 0, bgSpace.paddingBottom)
}
override fun onTransitionCompleted(motionLayout: MotionLayout, currentId: Int) { }
override fun onTransitionTrigger(motionLayout: MotionLayout, triggerId: Int, positive: Boolean, progress: Float) { }
})
In the onTransitionChange I’m chaging the paddingBottom during the animation progress making the parallax effect working as expected is not perfect but I just wanted to show this little detail in this post I hope you find this useful for your next project including the MotionLayout library which is really great and has a lot of potential!
You can check the project on Github link