Android-developer in an IT-company
Today, animated objects can be found in almost any product: they teach you how to navigate the functionality, focus on the right details, and help brand recognition.
When it comes to implementing a fairly complex UI, to layout the screen, developers most often use ConstraintLayout – a fairly popular and flexible tool that provides ample layout options.
Official documentation: ConstraintLayout is a ViewGroup that gives you the flexibility to move widgets and resize them. It allows you to get rid of a large number of nestings and improves layout performance. Summer 2020 saw a stable release of ConstraintLayout 2.0 which allows you to set animation to objects using MotionLayout.
Official documentation: MotionLayout is a layout that extends ConstraintLayout and allows you to animate layouts between different states. And to create animation, in most cases, only layout in an XML file is enough. Thus, the program code becomes cleaner and more concise.
Another notable innovation was the MotionEditor – a simple interface for managing elements from the MotionLayout library. MotionEditor is available in AndroidStudio starting from version 4.0, and in this article we will also cover how to work with it.
Let’s do animations
Below, we will create a simulator for cats that will catch a mouse with their paws. For clarity, the MotionLayout application will almost entirely consist of animations. The necessary conditions are:
- availability of AndroidStudio 4.0+;
- basic knowledge of ConstraintLayout;
- a bit of Kotlin.
For starters, you need to add the dependency to gradle:
Before animating, let’s prepare the initial state of the screen in a ConstraintLayout, add an ImageView with a mouse, and define its initial position at the top of the screen.
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <ImageView android:id="@+id/mouse" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/ic_mouse" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:ignore="ContentDescription" /> </androidx.constraintlayout.widget.ConstraintLayout>
We will do the main work in a file with motion_scene. To create it, you need to right-click on the preview of the object that you want to animate (our mouse), and select the Convert to MotionLayout item in the drop-down list.
Our root ConstraintLayout has been converted to a MotionLayout, and the LayoutDescription attribute points to the generated layout_scene-file. We will work with it.
This file contains everything you need to create an animation:
- the MotionScene root element;
- ConstraintSets – settings for describing the state of the screen;
- Transition – transition between states;
- KeyFrames and other attributes.
A powerful tool for configuring MotionLayot is the already mentioned MotionEditor. On the editor screen, you can see the start and end states. Between them is an arrow that shows the transition between states, or Transiton.
You can edit Transitions and ConstraintSets using the panel on the right where various attributes (properties) are set: constraintSetStart is the starting position of our mouse (in our case, the top of the screen). We define the final position of the object (constraintSetEnd) at the bottom of the screen – for this we add the necessary bindings:
<ConstraintSet android:id="@+id/end"> <Constraint android:id="@+id/mouse"> <Layout android:layout_width="50dp" android:layout_height="50dp" motion:layout_constraintBottom_toBottomOf="parent" /> </Constraint> </ConstraintSet>
Let’s start Transition and see what will happen on the screen – our mouse runs from top to bottom.
Let’s continue to complicate our animation and make our object move in the opposite direction. To do this, configure Transition by clicking on the icon in the upper left corner:
- We specify end as the initial ConstraintSet, and as the final – the starting one.
- In the Automatically field (the autoTransition attribute in the XML file), select Animate to End so that the animation plays from start to finish.
- In the code, add the duration value (animation duration in milliseconds) – 5000, so that the mouse moves a little slower.
Note that the autoTransition attribute allows transitions to run automatically one after the other. Thus, the state of the object will change from start to end and vice versa automatically.
<Transition motion:autoTransition="animateToEnd" motion:constraintSetEnd="@+id/end" motion:constraintSetStart="@id/start" motion:duration="5000"> <KeyFrameSet /> </Transition> <Transition motion:autoTransition="animateToEnd" motion:constraintSetEnd="@+id/start" motion:constraintSetStart="@+id/end" motion:duration="5000" />
Now I suggest that we make the trajectory of the object more diverse. What are the options? You can set more intermediate ConstraintSets with different mouse positions on the screen, but this approach will not be the most convenient since we need to define a large number of intermediate states between the main ones. The creators of MotionLayout suggest using more lightweight KeyFrames.
KeyFrames define the position or properties of an object, as well as changes at a specific transition (animation) time point.
In order to open the KeyFrame creation window from the editor, you need to select the Transition we need, indicate a point on the timeline (it is at this moment of the transition that the KeyFrame is applied) and click on the create button in the upper right corner.
Let’s configure KeyFrame in the opened window:
- Select from the KeyPosition list because we want to set the position of the object on the screen. This attribute indicates at what point in the Transition execution the KeyFrame is applied. The value can range from 0 to 100.
- Select the pathRelative coordinate system so that the movement of the object deviates from the main path in different directions.
- Fill in the percentX and percentY attributes — the position of the object as a pair of coordinates (x, y). Note that values can be negative.
The percentX and percentY values depend on the coordinate system we specify in the KeyPosition attribute:
- parentRelative – coordinates indicate the position relative to the parent container where the View is located;
- deltaRelative – coordinates indicate the position relative to the start and end positions as a percentage;
- pathRelative – coordinates indicate the position relative to the path from the start to end position of the object.
Each KeyFrame is independent of the others, so you can use a different coordinate system for each one. It will depend on the tasks you are solving.
So, the first KeyPosition is there! Let’s set a few more so that the mouse moves smoothly from side to side. By the way, MotionEditor has the ability to manually drag an object to the desired position when defining KeyFrames.
After defining random KeyFrames on both Transitions, we get the animation of the “running away” mouse.
Object property changes
Now let’s complicate our animation by changing the properties of the object. To do this, add another ImageView to the layout file, let it be a ball. Cats run after balls, don’t they?
Let’s make the ball fly from the lower left corner to the upper right. To do this, add a new Constraint to the existing ConstraintSets and use the Layout tag to specify the necessary bindings:
<Transition motion:autoTransition="animateToEnd" motion:constraintSetEnd="@+id/start" motion:constraintSetStart="@+id/end" motion:duration="5000" /><ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/mouse"> <Layout android:layout_width="50dp" android:layout_height="50dp" motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintTop_toTopOf="parent" /> </Constraint> <Constraint android:id="@+id/ball"> <Layout android:layout_width="50dp" android:layout_height="50dp" motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintStart_toStartOf="parent" /> </Constraint> </ConstraintSet>
Let’s add KeyAttributes to our transitions. This will change the properties of the ball during transitions in the same way as KeyPositions. In the KeyAttributes configuration window, select the id of our ball and the property. You can resize, add rotation, transparency, shadow and other object properties. For example, in order to rotate the ball 180 degrees, in the Attributes menu, specify the rotation value 180.
In the same way, we will change the size of the ball (scaleX, scaleY) and transparency (alpha) at different time points of our transitions. Randomly add new KeyFrames.
<KeyAttribute motion:motionTarget="@+id/ball" motion:framePosition="10" android:alpha="0.5" /> <KeyAttribute motion:motionTarget="@+id/ball" motion:framePosition="25" android:alpha="0" /> <KeyAttribute motion:motionTarget="@+id/ball" motion:framePosition="45" android:alpha="0.5" /> <KeyAttribute motion:motionTarget="@+id/ball" motion:framePosition="45" android:scaleX="0.5" android:scaleY="0.5" /> <KeyAttribute motion:motionTarget="@+id/ball" motion:framePosition="75" android:scaleX="1.5" android:scaleY="1.5" />
Let’s add a counter of clicks on objects. To do this, let’s hang ClickListeners on the mouse and the ball, inside which we will increase the value of the counter by one and display it on the screen. Let’s add a background and our kitty mini app is ready!
So, we have reviewed the basic concepts of MotionLayot and written a mini-game for a cat using only layout!
You can continue your learning, for example, consider CustomAttribute, OnSwipe, OnClick, using MotionLayout along with CoordinatorLayout, DrawerLayout, ViewPager, and more. All this will allow you to create a variety of animated effects: for example, a hidden toolbar, changing the background color, animation on click and swipe, animation when turning pages, opening and closing menus, and much more. In doing so, you will maintain simple UI logic and save a lot of code. Have fun!