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