MotionLayout: A new way to create animations on Android
source link: https://blog.autsoft.hu/motionlayout-a-new-way-to-create-animations-on-android/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Oh, no! A new animation framework for Android, again? We have quite a few already, do we really need a new one? First, let's see the previous approaches we used to create animations in our applications!
Animation solutions so far
ObjectAnimator
This subclass of ValueAnimator
provides support for animating properties on target objects. We can define it in XML files:
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android" android:duration="1000" android:propertyName="y" android:repeatCount="1" android:repeatMode="reverse" android:valueTo="200" android:valueType="floatType" />
Or we can use it from code:
ObjectAnimator.ofFloat(view, "translationX", 100f).apply { duration = 2000 start() }
ObjectAnimator
uses reflection to set the property given by the name as String
. Or we can avoid reflection by using the inbuilt wrapper property instead of hardcoding the property name.
ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 100f).apply { duration = 2000 start() }
This way the property value changes using the setter directly.
The biggest problem besides reflection is that you need a new ObjectAnimator
for every view you want to animate, because it doesn't support simultaneous changes of several objects. However, there's definitely a pro for ObjectAnimator
too: it can animate a property of any type.
The Animation class and its descendants
The biggest downside here is that you can animate only one property at once. You can use RotateAnimation
, AlphaAnimation
, ScaleAnimation
, etc., to animate basic properties. And still, you need multiple instances of the Animation
class for each property to play an AnimationSet
. The other thing is, that it can be used only on View
descendants.
ViewPropertyAnimator
Finally, we can animate multiple properties at once! :) ViewPropertyAnimator
was created to replace ObjectAnimator
, and it can modify the defined properties simultaneously. Furthermore, it's more effective due to optimized method calls. Last but not least, the syntax is much cleaner and readable.
This is how we use ObjectAnimator
to animate multiple properties, making use of an AnimatorSet
:
val animX = ObjectAnimator.ofFloat(view, "x", 50f) val animY = ObjectAnimator.ofFloat(view, "y", 100f) AnimatorSet().apply { playTogether(animX, animY) start() }
And the same using the ViewPropertyAnimator
framework:
view.animate().x(50f).y(100f)
ValueAnimator
ValueAnimator
allows us to animate any number of objects of any type at the same time using one instance of it.
ValueAnimator.ofFloat(0f, 3f).apply { duration = 3000 addUpdateListener { animation -> view.translationX = animation.animatedValue as Float } repeatCount = 5 }.start()
So, these are the most commonly used animation solutions, which are still good, but for different cases.
Let's jump into that new one.
♪Here come the Men in Black...♪
Here comes the MotionLayout
.
MotionLayout
MotionLayout
was introduced in 2018 at Google I/O . At the time of writing this article, it's on version 2.0.0 Beta 3.
dependencies { implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta3' }
As we can see from the dependency, MotionLayout
is a kind of ConstraintLayout
, that's why it's included in the ConstraintLayout
library. More precisely, it's a descendant of ConstraintLayout
, that extends the parent's functionality with animation capability.
Layout structure
To get started, in the screen's layout XML file we define a MotionLayout
, and we add all the necessary views to the layout. In this layout file, we constrain only those views which are not animated. The rest - which are animated - are constrained in the MotionScene
file. That's where the magic (at least the definition of the animations) happens, and we'll look at it in a moment.
That's why it's important to link the MotionScene
to the MotionLayout
by setting the layoutDescription
attribute of the MotionLayout
.
<androidx.constraintlayout.motion.widget.MotionLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" app:layoutDescription="@xml/motion_scene" app:motionDebug="SHOW_ALL">
There's also a debug option here (the motionDebug
attribute) to help visualize the paths of the animated views and the progress of the animation.
MotionScene
The MotionScene
has three important parts:
ConstraintSet Transition
ConstraintSet
This class allows you to define a set of constraints programmatically to be used with ConstraintLayout
. If we define constraints in the layout file as well as in the MotionScene
, the latter overrides the former.
We have two options to define the ConstraintSet
descriptors:
- If the scene has a lot of views to animate, it can be clearer to define the constraints in separate layout files. Just create the layout as you would using a
ConstraintLayout
as the root layout. - Alternatively, just define constraints inside
MotionScene
, inConstraintSet
s.
<ConstraintSet android:id="@+id/start"> <Constraint android:id="@id/imageView" android:layout_width="80dp" android:layout_height="80dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" /> </ConstraintSet> <ConstraintSet android:id="@+id/end"> <Constraint android:id="@id/imageView" android:layout_width="80dp" android:layout_height="80dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> </ConstraintSet>
Here we can define some basic attributes beside constraints:
- width, height,
- visibility, alpha, elevation, rotation, scale, translation
(At the time of writing this article, based on my experience, the elevation attribute isn't working.)
If we need other attributes apart from these basic ones, we have to define a CustomAttribute
like this:
<Constraint android:id="@+id/button" android:layout_width="64dp" android:layout_height="64dp" android:layout_marginStart="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <CustomAttribute app:attributeName="backgroundColor" app:customColorValue="#D81B60"/> </Constraint>
Transition
The Transition
in the MotionScene
will define how we get from one state to the other. There are two main attributes here:
app:constraintSetStart="@+id/start" app:constraintSetEnd="@+id/end"
These define the start and the end state of the animation. If we chose to define the constraints in layout files, we need to add the IDs of the layout files here, otherwise, the IDs of the ConstraintSet
s. There are also other attributes for setting the duration and the interpolator of the transition.
Inside the Transition
tag we can define multiple elements to customize the transition's behavior (so, to define the animated view's movement).
<Transition app:constraintSetEnd="@+id/end" app:constraintSetStart="@+id/start" app:duration="3000"> </Transition>
First of all, we can define two kinds of event handlers in case we want to control the animation.
-
OnClick
: The animation will be played as the user clicks on the target view. AclickAction
can be defined as well to modify the behaviour of the click event.
<Transition app:constraintSetEnd="@+id/end" app:constraintSetStart="@+id/start" app:duration="3000"> <OnClick app:clickAction="toggle" app:targetId="@id/imageView" /> </Transition>
-
OnSwipe
: This event handler catches drag events on the defined view. We can set the following attributes:-
dragDirection
: the progress direction of the drag, for example,app:dragDirection="dragRight"
means that progress increases as you drag to the right, -
touchAnchorId
: the ID of the view we want to drag, -
onTouchUp
: what happens with the animation when the user releases the dragged view.
-
<Transition app:constraintSetEnd="@+id/end" app:constraintSetStart="@+id/start" app:duration="3000"> <OnSwipe app:dragDirection="dragRight" app:onTouchUp="stop" app:touchAnchorId="@id/imageView" /> </Transition>
To customize the paths or attributes of the animated views, we can define KeyPosition
s, KeyAttribute
s, and CustomAttribute
s inside KeyFrameSet
tag.
With KeyPosition
we can define a specific point on the screen, and the percentage when the animation has to reach that point. This point can be defined in three coordinate systems:
parentRelative pathRelative deltaRelative
The official documentation explains them in detail.
Besides path points, we can define the attributes which will be animated. For basic attributes we use the KeyAttribute
tag, and set a framePosition
, a motionTarget
, and the animated attribute. If we need some other view parameter to animate, we can insert a CustomAttribute
tag inside KeyAttribute
like this:
<KeyAttribute app:framePosition="25" app:motionTarget="@+id/imageView"> <CustomAttribute app:attributeName="BackgroundColor" app:customColorValue="@color/brand_alpha" /> </KeyAttribute>
We have to specify the attribute's name as a string value and depending on the type of the attribute, specify the corresponding custom Type Value parameter with the value as seen above.
Motion Editor
Starting in Android Studio 4.0 Canary 1, we have a new tool to create and edit MotionLayout
on a graphical user interface. This is basically the layout designer view for MotionLayout
.
On the left side, there's an editable preview window, the same that we know from the ConstraintLayout
editor. To its right, the top half of the screen is for the visualization of the MotionLayout
, showing the start and the end states we've defined ( ConstraintSet
s) and the transition between them. Depending on what you select from these components (the selected item will be highlighted), the content will change below.
When...
- the
MotionLayout
is selected, we can see the views already added to the layout and their attributes. - one of the
ConstraintSet
s is selected, we can check if the views are constrainted, or set the necessary constraints and set custom attributes. - the transition is selected, a timeline appears, where we can define the
KeyFrameSet
's child tags (KeyPosition
,KeyAttribute
, etc.)
Finally, we can preview the defined motion frame by frame or played back and forth.
Conclusion
We've taken a look at the main features of MotionLayout
, the new animation framework on Android. It's the best solution for complex motion handling. In addition to describing transitions between layouts, MotionLayout
lets you animate all layout properties simultaneously. The best part of it is that it inherently supports seekable transitions . This means that you can instantly show any point within the transition based on some condition, such as touch input.
So let's make our applications more spectacular with some awesome interactive animation!
*All images in the MotionEditor section are from developer.android.com
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK