Photo by PAUL SMITH

MotionLayout and RecyclerView

Make a scene!

Farshad Tahmasbi
3 min readApr 3, 2020

--

Recently Google introduced MotionLayout and brought more fun and power to android animation API. MotionLayout is a subclass of ConstraintLayout that lets you define different states for layouts and transition between them.

In this article, I will focus on the transition of RecyclerView items in MotinLayout to make a nice scene, so If you are not familiar with MotionLayout yet I recommend you take time and check the official docs or read the articles from Nicolas Roard to learn the basics.

Let’s get started!

Since I’m interested in comics, I provided a sample app that presents a list of characters. when you click on any of them, after a transition you can see the details. All character resources including images and strings can be found on DC Comics. The sample code is available on Github.

If you want to get the advantage of the motion editor, you need to download the Android Studio (Canary Build) from here. At this moment version 4.1 CANARY 4 is available.

How the app will look like

As Nicolas Roard said:

MotionLayout will only provide its capabilities for its direct children — contrary to TransitionManager, which can work with nested layout hierarchies as well as Activity transitions.

Unfortunately we can’t apply motion to the RecyclerView’s children, So what’s the solution?

The main idea is something like SharedElements. We add a View to MotionLayout as a bridge, This View and the one in the RecyclerView should have the same dimension and position. In this way, we can keep the consistency of movements in animation.

In our case, we have an ImageView in the RecyclerView’s cells, So I added another ImageView to the MotionLayout (img_motion). When the list item is clicked we need to overlay the motion view over the fixed one, the dimension and position of them should be the same. With all those said the real challenge is to place the View in the right position.

I created an interface to listen to the items clicks and pass clicked View and its bound data to the Activity. First of all, we need to dynamically create start state constraints for the motion view. In this sample I used GridLayoutManager. Since the RecyclerView can be scrolling vertically, views have three possible states: not clipped, clipped from the top, clipped from the bottom.

Now that we know the clipping state, Let’s add constraints to the ImageView:

Here we get the start ConstraintSet and clear it to remove the previous constraints, then set the width and the height exact same as fixed ImageView, and considering clipping state, set the right constraints with its related margins, Views that are clipped from the top are connected to parent from the bottom, So we can use bottom margin to push them off the screen, the rest are connected to parent from the top.

Now update start state with the updated ConstraintSet, set the transition. We’re almost there!

Note that all we have done for motion ImageView was about start state because it needed to be set dynamically, So don’t forget to define its constraints for the end state.

Also, we need to set a transition listener to manipulate the motion and fixed ImageView opacity:

RecyclerView issues

Try to touch multiple items at the same time, What do you see!? Didn’t like it, right? So add this line to RecyclerView in the layout file:

android:splitMotionEvents="false"

While view is animating click another one, Not pretty! we need to somehow disable touch events on RecyclerView items until the transition is completed, Create another class and implement OnItemTouchListener like this:

Add it to the RecyclerView and keep a reference to enable and disable touch intercepts in the transition listener:

override fun onTransitionStarted(p0: MotionLayout?, start: Int, end: Int) {
if (start == startState) {
itemTouchInterceptor.enable()
img.alpha = 0.0f
img_motion.alpha = 1.0f
}
}

override fun onTransitionCompleted(p0: MotionLayout?, state: Int) {
if (state == startState) {
itemTouchInterceptor.disable()
img.alpha = 1.0f
img_motion.alpha = 0.0f
}
}

And it’s done! The sample code can be found on my Github, Thanks for reading.

--

--