Experiment #4: Expanding an image with MotionLayout - adrielcafe/gwent-wallpapers GitHub Wiki
That was my first time using MotionLayout, nothing too complex but a solid way to start. The animation I did consists on expand (to fullscreen) and shrink (to its original size) an ImageView. Here's the final result:
To achieve this animation, I use a single ImageView on top of a RecyclerView. When a wallpaper is selected I manually place the ImageView on top of the selected wallpaper (with the same size and aspect ratio) and start the expanding animation.
Let's see, step by step, how to implement this animation:
Step 1: the layout
The MotionLayout must contain a RecyclerView and an ImageView:
<androidx.constraintlayout.motion.widget.MotionLayout
app:layoutDescription="@xml/wallpaper_scene">
<androidx.recyclerview.widget.RecyclerView/>
<androidx.appcompat.widget.AppCompatImageView/>
</androidx.constraintlayout.motion.widget.MotionLayout>
Full implementation on section_wallpapers.xml
Step 2: the scene
In the MotionScene we need to declare two ConstraintSets (for the start and end animations) and a Transition (to specify the ConstraintSets, animation duration and other attributes).
The animation should start with the ImageView invisible. The value of width and height will be defined at runtime (explained in the next step).
The animation should end with the ImageView visible. The value of width and height must be match_parent.
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetStart="@id/start"
app:constraintSetEnd="@+id/end"/>
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/currentWallpaper">
<PropertySet android:visibility="invisible"/>
</Constraint>
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint
android:id="@id/currentWallpaper"
android:layout_width="match_parent"
android:layout_height="match_parent">
<PropertySet android:visibility="visible"/>
</Constraint>
</ConstraintSet>
</MotionScene>
Full implementation on wallpaper_scene.xml
Step 3: positioning the ImageView
Let's start by creating an extension property which returns the coordinates of any View in the window:
val View.windowLocation: PointF
get() {
val (x, y) = IntArray(2)
.also(::getLocationInWindow)
.map(Int::toFloat)
return PointF(x, y)
}
Now we get the coordinates of the selected wallpaper and change the translationX and translationY values of the ImageView used in the start ConstraintSet:
val itemWindowLocation = itemBinding.image.windowLocation
sectionBinding.root
.getConstraintSet(R.id.start)
.setTranslation(R.id.currentWallpaper, itemWindowLocation.x, itemWindowLocation.y)
Full implementation on WallpaperExpanderHelper.kt
Step 4: expanding and shrinking
It's done! We can now control the animation programmatically:
// Expanding
sectionBinding.root.transitionToEnd()
// Shrinking
sectionBinding.root.transitionToStart()
Conclusion
MotionLayout is very powerful and simpler to use than I thought. I'll definitely dive into it from now on.