This article has been published exclusively by guolin_blog, an official wechat account

Android MotionLayout is a guide to the latest Android animation layout!

The environment

  • system : macOS
  • Android studio: 4.1.3
  • ConstraintLayout: 2.0.4
  • Gradle: gradle – 6.7.1 – bin
  • Kotlin: 1.4.23

What do you need to know

  • MotionLayout is a subclass of ConstraintLayout and has all the functions of ConstraintLayout

    Implementation ‘androidx. Constraintlayout: constraintlayout: 2.0.4’

  • Preview MotionLayout with AS. The AS version must be >= 4.0

Create an animation using MotionLayout

Convert ConstraintLayout to MotionLayout

After conversion:

So you can see that we’ve defined a Button, and if we say ConstraintLayout, we’ll warn you to constrain it, but we don’t

This is because the MotionLayout property is defined in the layoutDescription property

Click animation [OnClick]

OnClick# clickAction description:

type instructions rendering
motion:clickAction=”toggle reverse
motion:clickAction=”transitionToEnd Animate to the second
motion:clickAction=”jumpToEnd End without adding animation
motion:clickAction=”jumpToStart Do not add animation to start The name implies…
motion:clickAction=”transitionToStart Add an animation to The name implies…

Multiple Views are linked together

For example:

Effect:

Finger drag [OnSwipe]

Parameter Description:

  • Motion :touchAnchorId refers to a view that you can slide and drag.
  • Motion :touchAnchorSide means we drag the view from the right.
  • Motion :dragDirection Indicates the drag progress direction. For example, motion:dragDirection=”dragRight” means that progress increases as you drag to the right.

Effect:

Auxiliary tool

MotionLayout comes with debugging tools by default.

Description table:

state instructions rendering
app:motionDebug=”SHOW_ALL Auxiliary lines and progress display
app:motionDebug=”SHOW_PATH Auxiliary line display
app:motionDebug=”SHOW_PROGRESS According to schedule

Official illustration:

  • The circle represents the starting or ending position of a view.
  • Lines represent the path of a view.
  • The diamond represents the path where KeyPosition is modified.

There is another way to debug using views:




Modifying path (KeyPosition)

The path can be modified using the visualization tool on the right:




, a graph:

Explanation 1 Steps 2

Let’s look at the auto-generated code:

KeyPosition parameter description:

  • Motion :motionTarget=”@+id/button” The view that needs to move the track
  • Motion :framePosition=”[0-100]” framePosition is a number between 0 and 100. It defines the time to be applied in the animation KeyPosition, with 1 representing 1% of the animation and 99 representing 99% of the animation
  • motion:keyPositionType=” ”KeyPositionType This is how KeyPosition modifies the path. It can beparentRelative.pathRelativeOr,deltaRelative
  • PercentX | percentY is to modify the path framePosition (value between 0.0 and 1.0, allows the negative value and value > 1)

Motion :keyPositionType=” ”

type instructions Coordinate system rendering
pathRelative It’s completely different from the other two because the X-axis follows the motion path from beginning to end. So, (0,0) is the starting position, and (1,0) is the ending position.
deltaRelative The term “deltaRelative” is also used to describe relative change. In deltaRelative coordinates, (0,0) is the starting position of the view and (1,1) is the ending position. The X and Y axes are aligned with the screen. The X-axis is always horizontal on the screen, the Y-axis is always vertical on the screen. The main difference from parentRelative is that coordinates describe only the part of the screen within which the view will move. The effect is similar to That of pathRelative, no demo
parentRelative ParentRelative uses the same coordinate system as the screen. It defines (0, 0) in the upper left corner of the entire MotionLayout and (1, 1) in the lower right corner.

ParentRelative: Four points are set here, respectively:

Assuming that we now move the starting point 1,deltaRelative builds a ‘Bezier environment’ from the starting point and the starting point 2 to generate the corresponding path, which is similar to the effect of pathRelative but different!

The difference between pathRelative and deltaRelative:

  • PathRelative does not depend on the start and end points to drag
  • DeltaRelative starting and ending points on the same X-axis will cause animation paths not to be executed on bezier paths. I’m going to execute a line.

As shown in the figure:

type instructions rendering
deltaRelative Depending on where you start and where you end
PathRelative or parentRelative You don’t have to rely on starting and ending points

The end result looks like this:




The effect is the same, just put one, need to download the source code to watch…

KeyPosition is advanced with pathMotionArc

PathMotionArc is used to draw arcs.

Let’s start with a simple example of pathMotionArc:




As you can see, drawing an elegant arc is as simple as setting motion at the start point :pathMotionArc=”startHorizontal”

⚠️: Motion :pathMotionArc requires 2 points, the default is the start point and the end point

How do you set multiple points? For example:

You can also set the scale of the two arcs




Location coordinate description:

PathMotionArc:

  • StartVertical Indicates a downward arc
  • StartHorizontal upward arc
  • None linear
  • If the previous point was startVertical, then the current point is startHorizontal, which can be understood as inversion
type instructions rendering
Effect of a The starting point: motion:pathMotionArc=”startVertical

The starting point 1: motion:pathMotionArc=”startHorizontal

The starting point 2: motion:pathMotionArc=”startHorizontal
Effects of two The starting point: motion:pathMotionArc=”startVertical

The starting point 1: motion:pathMotionArc=”none

The starting point 2: motion:pathMotionArc=”startHorizontal
The effect of three The starting point: motion:pathMotionArc=”startVertical

The starting point 1: motion:pathMotionArc=”startVertical

The starting point 2: motion:pathMotionArc=”flip

The code for effect 1, effect 2, and Effect 3 is highly repetitive, so here’s just the code for Effect 3:

Effect three code:


      
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">
    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <! -- Click event -->
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/button6" />
        <KeyFrameSet>
            <! -- Start point 1 and start point 2 set "association" -->
            <KeyPosition
                motion:framePosition="40"
                motion:keyPositionType="deltaRelative"
                motion:motionTarget="@+id/button6"
                motion:pathMotionArc="startVertical"
                motion:percentX="0.358"
                motion:percentY="0.17" />
            <! -- Start point 2 and end relation -->
            <KeyPosition
                motion:framePosition="79"
                motion:keyPositionType="deltaRelative"
                motion:motionTarget="@+id/button6"
                motion:pathMotionArc="flip"
                motion:percentX="0.675"
                motion:percentY="0.568" />
        </KeyFrameSet>
    </Transition>

    <! -- start -->
    <ConstraintSet android:id="@+id/start">
        <! Start point and start point 1 set "association" -->
        <Constraint
            android:id="@+id/button6"
            android:layout_width="80dp"
            android:layout_height="80dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:pathMotionArc="startVertical" />
    </ConstraintSet>
    
    <! -- end -->
    <! -- The end point does not set the association. -->
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/button6"
            android:layout_width="64dp"
            android:layout_height="64dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintRight_toRightOf="parent" />
    </ConstraintSet>
</MotionScene>
Copy the code

Here the ratio can also be changed, for example:




Changing the state of an attribute (KeyAttribute)

Take a look at the code:


      
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/ It's over"
        motion:constraintSetStart="@id/ Here we go"
        motion:duration="1000">
        <KeyFrameSet>

            <! -- Change attribute state -->
            <KeyAttribute
                motion:motionTarget="@+id/button7"
                motion:framePosition="22"
                android:alpha="0.2" />
        </KeyFrameSet>

        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/button7" />
    </Transition>

    <! It is also possible to define Chinese at the beginning, but it is not recommended. -->
    <ConstraintSet android:id="@+id/ Here we go">
        <Constraint
            android:id="@+id/button7"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>

    <! Chinese can also be defined here, but it is not recommended. -->
    <ConstraintSet android:id="@+id/ It's over">
        <Constraint
            android:id="@+id/button7"
            android:layout_width="60dp"
            android:layout_height="60dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
</MotionScene>
Copy the code

Change the alpha value:




You can also set more than one property, for example:




Explore by yourself:

  • android:visibility
  • android:alpha
  • android:elevation
  • android:rotation
  • android:rotationX
  • android:rotationY
  • android:scaleX
  • android:scaleY
  • android:translationX
  • android:translationY
  • android:translationZ

KeyAttribute works with CustomAttribute to set the color

  • CustomAttribute Is a KeyAttribute attribute that is used to set the view color

Let’s look at the code:


      
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <! - click - >
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/imageView8" />

        <KeyFrameSet>

            <! -- Turn 3 times to 100 points -->
            <KeyAttribute
                android:rotation="720"
                motion:framePosition="100"
                motion:motionTarget="@id/imageView8" />

            <! -- black at 0 o 'clock -->
            <KeyAttribute
                motion:framePosition="0"
                motion:motionTarget="@id/imageView8">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="# 000000" />
            </KeyAttribute>

            <! -- Red at 50 o 'clock -->
            <KeyAttribute
                motion:framePosition="50"
                motion:motionTarget="@id/imageView8">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="#E91E63" />
            </KeyAttribute>

            <! -- Black at 100 -->
            <KeyAttribute
                motion:framePosition="100"
                motion:motionTarget="@id/imageView8">
                <CustomAttribute
                    motion:attributeName="colorFilter"
                    motion:customColorValue="# 000000" />
            </KeyAttribute>
        </KeyFrameSet>
    </Transition>
    
    <ConstraintSet android:id="@+id/start" . />
   
    <ConstraintSet android:id="@+id/end" . />
</MotionScene>
Copy the code

Inside the CustomAttribute you must specify an attributeName and a value to set.

  • Motion :attributeName is the name of the setter that this custom attribute will call. In this example, setColorFilteronDrawable will be called.
  • motion:customColorValueIs a custom value for the type indicated in the name, or in this case, the specified color. Custom values can have any of the following types:
    • Color
    • Integer
    • Float
    • String
    • Dimension
    • Boolean

Take a look at the renderings:

Set jitter [KeyCycle]

How to create:

Key code:

<Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <! - click - >
        <OnClick . />

        <KeyFrameSet>
            <! -- waveOffset added to the property's offset motion:wavePeriod Number of loops to loop around this region Motion :wavePeriod ="cos" Sin | square | triangle | sawtooth | reverseSawtooth | cos | | bounce sine square reverse sawtooth | | | | serrated triangle cosine | rebound -- -- >
              <KeyCycle
                android:alpha="0.5"
                android:scaleY="1.2"
                android:scaleX="1.2"
                motion:framePosition="51"
                motion:motionTarget="@+id/imageView8"
                motion:waveOffset="2"
                motion:wavePeriod="1"
                motion:waveShape="sin" />
        </KeyFrameSet>
    </Transition>
Copy the code

Parameter Description:

  • Motion :waveOffset added to the offset value of the property
  • Motion :wavePeriod Number of loops to loop around this region
  • motion:waveShape=”cos”
    • sin|square|triangle|sawtooth|reverseSawtooth|cos|bounce

Effect pictures of various types:

sin square triangle sawtooth reverseSawtooth cos bounce
sine square triangle The sawtooth Reverse the sawtooth cos rebound

Set Jitter (KeyTimeCycle)

KeyTimeCycle works the same as KeyCycle, and the parameters are the same.

A little different is that KeyTimeCycle is usually used together with three KeyTimeCycle cycles to define an exact keyframe

It will only change at frame 50 when the Motion :wavePeriod is set to 1

  • Motion :wavePeriod: Number of loops to loop around this region

Changing control properties (KeyTrigger)

What does it mean to change control properties? How to control?

First, the effect:

Let’s start by customizing ImageView with two methods: show and hide

class KeyTriggerImageView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) :
    AppCompatImageView(context, attrs, defStyleAttr) {

    / / display the view
    fun show(a) {
        visibility = View.VISIBLE
    }
    // Hide the current view
    fun hide(a) {
        visibility = View.GONE
    }
}
Copy the code

How to use:

<? xml version="1.0" encoding="utf-8"? > <MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="2000"> <! <OnClick Motion :clickAction="toggle"
            motion:targetId="@id/imageView11" />
        <KeyFrameSet>
            <KeyTrigger
                motion:framePosition="0"
                motion:motionTarget="@+id/imageView11"
                motion:onCross="show" />
            <KeyTrigger
                motion:framePosition="20"
                motion:motionTarget="@+id/imageView11"
                motion:onCross="hide" />
            <KeyTrigger
                motion:framePosition="60"
                motion:motionTarget="@+id/imageView11"
                motion:onCross="show" />
            <KeyTrigger
                motion:framePosition="79"
                motion:motionTarget="@+id/imageView11"
                motion:onCross="hide" />
            <KeyTrigger
                motion:framePosition="100"
                motion:motionTarget="@+id/imageView11"
                motion:onCross="show" />
        </KeyFrameSet>
    </Transition>

    <ConstraintSet android:id="@+id/start". /> <ConstraintSet android:id="@+id/end". /> </MotionScene>Copy the code

KeyTrigger parameters:

  • Motion: the name of the method called by onCross
  • What frame is currently in (0-100)
  • Motion: Id of the control set by motionTarget

Take a look at the results:




According to this idea, it is possible to replace the icon display in the process of sliding, for example:




One more auxiliary image, it should be very clear now!!

Acceleration and deceleration (Easing)

Also a parameter effect: very simple to use, directly look at the code:


      
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="1000">
        <! - click - >
        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/imageView13" />
        <KeyFrameSet>

        </KeyFrameSet>
    </Transition>
    <! - began to -- -- >
    <ConstraintSet android:id="@+id/start">
  		<! -- Motion :transitionEasing Sets the acceleration or deceleration type -->
        <Constraint
            android:id="@+id/imageView13"
            android:layout_width="100dp"
            android:layout_height="100dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:transitionEasing="acclerate" />
    </ConstraintSet>
    <! End -- -- -- >
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/imageView13"
            android:layout_width="100dp"
            android:layout_height="100dp"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
    </ConstraintSet>
</MotionScene>
Copy the code

Motion :transitionEasing

type The auxiliary figure The effect
standard
acclerate
decelerate

Common on these kinds, there are many others, not a demonstration.

The effect changes are very subtle, their own manual make a understand!!

In actual combat

Let’s take a look at the actual results:




The layout looks like this:

Demand analysis:

  • Similar to mantis shrimp click comment function
  • When you click the comment button, the image shrinks and a RecyclerView pops up underneath to display the comment
  • Comment button not displayed when RecyclerView is displayed
  • Recyclerview is not displayed when the comment button is displayed

activity_motion_layout_9_scene.xml:


      
<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:motion="http://schemas.android.com/apk/res-auto">

    <Transition
        motion:constraintSetEnd="@+id/end"
        motion:constraintSetStart="@id/start"
        motion:duration="400">

        <OnClick
            motion:clickAction="toggle"
            motion:targetId="@id/imageComment" />

        <OnSwipe
            motion:dragDirection="dragUp"
            motion:touchAnchorId="@id/recyclerView" />
    </Transition>

    <! - began to -- -- >
    <ConstraintSet android:id="@+id/start">
        <Constraint
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />

        <Constraint
            android:id="@+id/imageComment"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginRight="@dimen/dp_10"
            android:alpha="1"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintVertical_bias="0.7" />
        <Constraint
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            motion:layout_constraintTop_toBottomOf="@id/image" />
    </ConstraintSet>

    <! End -- -- -- >
    <ConstraintSet android:id="@+id/end">
        <Constraint
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            motion:layout_constraintBottom_toTopOf="@id/recyclerView"
            motion:layout_constraintLeft_toLeftOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent" />
        <Constraint
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="500dp"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintTop_toBottomOf="@id/image" />

        <Constraint
            android:id="@+id/imageComment"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginRight="@dimen/dp_10"
            android:alpha="0"
            motion:layout_constraintBottom_toBottomOf="parent"
            motion:layout_constraintRight_toRightOf="parent"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintVertical_bias="0.7" />
    </ConstraintSet>
</MotionScene>
Copy the code

This is basically the end of it

conclusion

General structure

<Transition
    app:constraintSetStart="@+id/start"
    app:constraintSetEnd="@+id/end"
    app:duration="1000">
	<! - drag - >
    <OnSwipe />

	<! - click - >
    <OnClick />
    <KeyFrameSet >
    
	        <KeyAttribute>
	            	<CustomAttribute/>
	        </KeyAttribute>
	        
	        <KeyPostion/>
	        
	        <KeyCycle/>
	        
	        <KeyTimeCycle/>
    </KeyFrameSet>
    
	    <! -- Start point state parameter configuration for transition animation -->
	   <Constraint android:id="@id/viewId">
			    <! -- Motion model: arc path, time model, etc.
			    <Motion/>
			    <! < span style = "box-sizing: border-box; color: RGB (51, 51, 51); display: block; line-height: 22px; font-size: 14px! Important; word-break: normal; word-break: normal;
			    <Layout/>
			    <! -- Animation transform: Do rotation, displacement, scaling, sea altitude and other properties -->
			    <Transform/>
			    <! -- Custom attributeName adds set/get reflection to find the real function name, SetBackgroundColor () custom(XXX)Value specifies the data type of the property -->
			    <CustomAttribute/>
			    <! -- Specific properties visibility, alpha, etc. -->
			    <PropertySet/>
	</Constraint>
    
    <! -- End point state parameter configuration for transition animation -->
    <ConstraintSet android:id="@+id/end"./>
</Transition>
Copy the code

The complete code

Other:

  • Git Clone github.com/googlecodel…

  • The official documentation

  • Reference document 1

  • Reference 2

Original is not easy, your praise is the biggest support for me!