Jetpack Compose: Anchored Draggable Item in MotionLayout Part 1

Aung Thiha
6 min readSep 15, 2023

Do you need a draggable item that can have its contents animated? You have come to the right place.

This is what we’re going to build in this article. As you might have guessed from the title, we’re gonna use MotionLayout, which is a subclass of ConstraintLayout.

This article is inspired by an article written by Radhika S but the onSwipe attribute in MotionScene file demonstrated in that article does not work. So, I decided to find a solution and write an article.

Enough of the backstory; let’s dive in.

Create Project

  • Please, go ahead and create your project in Android Studio.
  • Make sure you choose “Compose Empty Activity”.

Create Layout

To create the layout, first, add dependency for MotionLayout

implementation "androidx.constraintlayout:constraintlayout-compose:1.0.1"

Please, grab the cake photo from here and add it into your drawable folder. (I’m referencing the cake photo from Github repo of where I got the UI idea from. If the photo is no longer available, please let me know.)

After that, add the layout file itself.

When you import missing classes, make sure you choose them from compose packages. There’s usually similar classes with the same names in other packages. So, please, make sure you choose the ones from compose package.

Don’t worry about the empty string in remember and the 0f progress passed into MotionLayout for now. We’ll work on them later.

layoutId is used to help MotionLayout identify the items, just like we do with ConstraintLayout.

Alright, let’s use that RecipeDetail compose in the root of your compose. In my case, this is what it looks like

Let’s run the project. Wait!!! What’s going on?!!! Blank white screen!!!

Worry not! It’s happening because we haven’t placed any constraints yet.

MotionScene

To define constraints, we need to create what they call MotionScene.

There are two ways to do it:

  1. MotionCompose
  2. Json

We’ll use json in this article.

In MotionScene, we need to define start point and end point. MotionLayout will figure out how to animate between these two points. These points are known as ConstraintSet. You can have more than two ConstraintSets when you need to but that’s a story for another day.

Let’s start with the start point. This file should be inside res/raw and the file extension should be json5.

in top: ['parent', 'top', 200], 200 is a density pixel(dp). It’s the margin to the top of parent.

the value of height is also dp.

(I hope the rest of this file is self-explanatory. If it’s not clear, I would really appreciate it if you let me know. That will help me improve my blogging skills)

For the end point, just put in the same content as start point for now. We just wanna display the initial UI at this stage.

Next, let’s use the MotionScene json5 file in MotionLayout. Remember the empty string at the top of the RecipeDetail compose function. That’s where we use this json5 file.

Let’s run the project. The UI isn’t exciting at this point and you can’t drag anything yet. There’s not even a card UI. We’ll add a card in the next stage.

Card

Normally the title, the action buttons and the body message would be inside Card compose but because MotionLayout can’t animate nested items. We need to make the layout flat.

Let’s add the card UI with Box below the cake Image and above the title Text. The reason it needs to be after Image is because we want the card to be on top of the Cake. Similarly, we want the card to be underneath the texts.

Just like our first step, the card won’t show up because we don’t have constraints for it yet.

Let’s add constraint for the card in the MotionScene file.

You should now see the card but you can’t drag it yet

Make The Card Draggable

First, we need to define end point in the MotionScene.

Unlike top, height, end, etc., the value of translationY is not density pixel. It’s pixel.

You won’t be able to drag yet but to make sure the constraints of end point is correct, please, switch the contents of start and end temporarily and run the project to see if the constraints for end point is correct.

Notice that the date is hidden. We set the visibility of date to be gone . MotionLayout is smart enough find out how to transition into hidden state smoothly.

The background of the action buttons doesn’t turn red and its height is not the same as in the video. We’ll fix that later.

After you’ve checked the constraints for end point are correct, please, switch the contents of start and end back.

To make the card draggable, next step is to use AnchoredDraggable from compose foundation library.

Please, be aware that this is an experimental API but since Swipeable is deprecated, AnchoredDraggable is the only thing we can use with MotionLayout for this kind of animation unless you implement everything from scratch. (If you do find a different solution that doesn’t involve an experimental API and it’s not writing the whole thing from scratch, please, do let me know in the comment section.)

https://developer.android.com/jetpack/compose/touch-input/pointer-input/migrate-swipeable

Anyway, I’ll use AnchoredDraggable for this article. Please, import compose foundation library. At the time of writing this article, stable version does not have AnchoredDraggable yet. So, I’m using alpha.

implementation 'androidx.compose.foundation:foundation:1.6.0-alpha05'

Please, copy-paste AnchoredDraggableCardState.kt into your project. We’ll use these enum values as keys when we tell AnchoredDraggable API what the anchored points are.

Because we want the gap between the top and the card to be 200dp when the card is dragged down, we set draggedDownAnchorTop to AnchoredDraggableCardState.DRAGGED_DOWN inside DraggableAnchors.

When the card is dragged up, it’s obvious there will be no gap between the top and the card. So, AnchoredDraggableCardState.DRAGGED_UP has 0f.

Please, be aware that these anchor points are not actually used to move the card. Rather, they are used to move the gesture listener area. The card itself is moved by the MotionLayout but we need to move the gesture listener area too because we want the card to move only when the user drags by the card, not when they drag by other areas.

Back to AnchoredDraggable, we need tell the AnchoredDraggable which point we want to start first. You can see we’re passing AnchoredDraggableCardState.Dragged_DOWN into AnchoredDraggableState.

For other arguments of AnchoredDraggableState, please, look into official document to understand more.

Notice that MotionLayout also now has proper progress set instead of 0f

Final step in making the card draggable is to set the AnchoredDraggableState onto the card.

Let’s run the project.

You’re probably get errors saying you need to update your compileSdk to 34. If you get it, please, update the compileSdk and targetSdk to 34 or higher if there’s higher version already released.

After updating the compileSdk and targetSdk, please, re-run the project.

If you can’t drag the card and if you’re seeing the card at the top, you probably forgot to switch the start and end back. Please, remember we temporarily switched them.

Whoo hoo!!! We can now drag the card!!!

I hope you enjoyed the article! Please, give it some claps and please, give me some feedbacks. So, I can improve my blogging skills to help fellow developers more and more.

In part 2, I explain how to increase the size of the background of action buttons and how to use a custom attribute from MotionScene to animate the color transition.

See you in part 2!

--

--

Aung Thiha

Android | Jetpack Compose | A/B Testing | TDD | CI/CD | SOLID | Clean Architecture | Design Patterns | Mentorship | Recruitment