I introduced MotionLayout in an article I translated before. If you don’t know what MotionLayout is, please read this article first

I want to introduce a cool page-turning animation with MotionLayout, translated from Medium

This is the effect

Slide cards

How to implement the above card

  • We use MotionLayout in the XML and define a FrameLayout
    <androidx.constraintlayout.motion.widget.MotionLayout
        android:id="@+id/motionLayout"
        app:layoutDescription="@xml/memory_curve_scene"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:motionDebug="SHOW_ALL">


        <FrameLayout
            android:id="@+id/bottomCard"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@color/design_default_color_primary">
        </FrameLayout>

    </androidx.constraintlayout.motion.widget.MotionLayout>
Copy the code

Notice the line app:motionDebug= “SHOW_ALL”, which allows us to display some properties of the current MotionLayout on the screen, such as progress and so on. Usually only used in Debug mode, remember to turn it off when the app is officially released.

Then add the initial ConstraintSet to the MotionScene file that we defined in the RES/XML directory

<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <ConstraintSet android:id="@+id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="50dp"
            android:layout_marginTop="50dp"
            android:layout_marginEnd="50dp"
            android:layout_marginBottom="50dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>
    </ConstraintSet>
</MotionScene>
Copy the code

Let’s add constraintSet to pass and constraintSet in the like state that we’re in when we slide completely to the left or right.

    <ConstraintSet android:id="@+id/pass"
        app:deriveConstraintsFrom="@id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="50dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="200dp"
            android:layout_marginBottom="80dp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintWidth_percent="0.7"/>

    </ConstraintSet>

    <ConstraintSet android:id="@+id/like"
        app:deriveConstraintsFrom="@id/rest">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginStart="200dp"
            android:layout_marginTop="20dp"
            android:layout_marginEnd="50dp"
            android:layout_marginBottom="80dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintWidth_percent="0.7"/>
    </ConstraintSet>
Copy the code

The pass and like states are actually two states of the mirror.

Pay attention to this line app: deriveConstraintsFrom = “@ id/rest”, it makes the ConstraintSet another ConstraintSet automatically inherit property, of course also can overwrite another ConstraintSet properties.

Now we have three constraintsets, which are REST, Like, and Pass. Now we need to transition a state with a click or a swipe

We added OnSwipe

    <Transition
        app:constraintSetEnd="@+id/pass"
        app:constraintSetStart="@+id/rest"
        app:duration="300" >
        <OnSwipe
            app:dragDirection="dragLeft"
            app:onTouchUp="autoComplete"
            app:touchAnchorId="@id/topCard"
            app:touchAnchorSide="left"
            app:touchRegionId="@id/topCard" />
    </Transition>
Copy the code

The like part is mirrored

Now we can do this

Make the card automatically fly off the screen

In order to make the card to fly out of the screen, we also need to add two ConstraintSet: offscreenLike and offscreenPass

    <ConstraintSet android:id="@+id/offscreenLike"
        app:deriveConstraintsFrom="@id/like">
        <Constraint
            android:id="@id/topCard"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_marginBottom="80dp"
            android:layout_marginEnd="50dp"
            android:layout_marginTop="20dp"
            app:layout_constraintStart_toEndOf="parent"
            app:layout_constraintWidth_percent="0.7" />
    </ConstraintSet>
Copy the code

When do we want the cards to fly off the screen? When the card is in the like phase or pass phase.

So we’re going to add Transition

    <Transition
        app:autoTransition="animateToEnd"
        app:constraintSetEnd="@+id/offscreenLike"
        app:constraintSetStart="@+id/like"
        app:duration="150" />
Copy the code

At the bottom of the card

We now need to add our bottom card in Layout.

        <FrameLayout
            android:id="@+id/topCard"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:background="@color/cardview_shadow_start_color">
        </FrameLayout>
Copy the code

In the MotionScene of RES/XML, we need to change each ConstraintSet

<ConstraintSet android:id="@id/rest"> <! -... --> <Constraint android:id="@id/bottomCard">
        <Layout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginBottom="50dp"
            android:layout_marginEnd="50dp"
            android:layout_marginStart="50dp"
            android:layout_marginTop="50dp" />
        <Transform
            android:scaleX="0.90"
            android:scaleY="0.90" />
    </Constraint>
</ConstraintSet>
<ConstraintSet
    android:id="@+id/offScreenLike"
    app:deriveConstraintsFrom="@id/like"> <! -... --> <Constraint android:id="@id/bottomCard">
        <Transform
            android:scaleX="1"
            android:scaleY="1" />
    </Constraint>
</ConstraintSet>
Copy the code

Allows cards to be infinite

We need to manually add code to reset the card back to the REST phase when it slides to the offscreenLike phase

private fun setInfinite() { motionLayout.setTransitionListener(object : TransitionAdapter() { override fun onTransitionCompleted(motionLayout: MotionLayout? , currentId: Int) { when (currentId) { R.id.offscreenLike, R.id.offscreenPass -> { motionLayout? .progress =0f motionLayout? .setTransition(R.id.rest, R.id.like) } } } }) }Copy the code

Add additional views

We’ll finally add two more views to the XML

        <ImageView
            android:id="@+id/likeIcon"
            android:src="@drawable/ic_baseline_favorite_border"
            android:tint="#fbc02d"
            android:background="@drawable/backround_circle"
            android:layout_height="0dp"
            android:layout_width="0dp" />
Copy the code

, then change the corresponding ConstraintSet in the MotionScene,

    <ConstraintSet android:id="@+id/like"
        app:deriveConstraintsFrom="@id/rest">... <Constraint android:id="@+id/likeIcon">

            <Layout
                android:layout_width="100dp"
                android:layout_height="100dp"
                app:layout_constraintBottom_toBottomOf="@id/topCard"
                app:layout_constraintEnd_toEndOf="@id/topCard"
                app:layout_constraintStart_toStartOf="@id/topCard"
                app:layout_constraintTop_toTopOf="@id/topCard" />

            <Transform
                android:scaleX="1"
                android:scaleY="1" />

            <PropertySet android:alpha="1" />

        </Constraint>
Copy the code

As well as

    <ConstraintSet android:id="@+id/rest">... <Constraint android:id="@+id/likeIcon">

            <Layout
                android:layout_width="40dp"
                android:layout_height="40dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <Transform
                android:scaleX="0.5"
                android:scaleY="0.5" />

            <PropertySet android:alpha="0" />


        </Constraint>
Copy the code

In this way, we have achieved the desired effect

Finally attached is the Github project address, which uses this animation

Reference: Medium