Blog Infos
Author
Published
Topics
, , , ,
Published

I recently had the opportunity to work on a custom “Slide to Book” button for one of my projects and since this was my first time implementing a swipe-to-action animation in Jetpack Compose, I thought I’d share the learnings and breakdown the implementation.

This button allows the user to drag a slider thumb horizontally to confirm a booking. Once dragged past a threshold, the button triggers a ride booking action, plays a subtle animation, and displays a loading indicator.

If you would like to skip this article and just want to see a gif of the end result and go straight for the final code, here’s the link.

Let’s get’s started!

Step 1: Create the Composable

We start by defining the top-level composable for the button. This composable is responsible for the layout and structure of the entire slider: the outer button that holds the text and slider, and the inner slider thumb that the user can drag. Let’s name it SlideToBookButton.

@Composable
fun SlideToBookButton(
btnText: String, // The text label shown on the button (e.g., "Book Ride ₹199")
btnTextStyle: TextStyle, // The style for the button text (font, weight, size, etc.)
outerBtnBackgroundColor: Color, // Background color for the outer track (entire button)
sliderBtnBackgroundColor: Color, // Background color for the draggable thumb
@DrawableRes sliderBtnIcon: Int, // Icon to show inside the draggable thumb
onBtnSwipe: () -> Unit // Callback triggered when swipe is completed
)

This composable includes:

  • btnText: The label displayed on the outer track (e.g., “Book Ride ₹199”).
  • btnTextStyle : The typography style for the btnText — allows font weight, size, etc.
  • outerBtnBackgroundColor : The background color for the entire button track.
  • sliderBtnBackgroundColor : The background color for the draggable slider/thumb element.
  • sliderBtnIcon : The icon displayed inside the slider button.
  • onBtnSwipe : The callback invoked when the user successfully completes the swipe.
Step 2: Define the button UI

With our parameters in place, the next step is to define the visual structure of the SlideToBookButton. The layout has two primary components:

  1. The outer button— a full-width container that holds the background and the center aligned button text.
  2. The slider button.thumb — a smaller draggable button layered above the track that users can slide horizontally.
/**
*
* This composable creates a custom, swipeable button that resembles a "slide to book" interaction.
* It is composed of two main parts:
* - The outer track (the full-width button background) which displays the label.
* - The inner slider thumb, which can be dragged from left to right.
*
*
* @param btnText Text to display on the outer button track (e.g., "Book Ride ₹199")
* @param btnTextStyle Text style for the button label (e.g., font weight, color)
* @param outerBtnBackgroundColor Background color for the full-width outer button
* @param sliderBtnBackgroundColor Background color for the draggable thumb button
* @param sliderBtnIcon Icon shown inside the slider thumb (e.g., car or arrow icon)
* @param onBtnSwipe Callback triggered once the user slides to complete the booking
*/
@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
}
}
/**
*
* This composable defines the visual appearance of the slider thumb — a small rounded box
* that contains an icon (usually a car or arrow). It is positioned inside the larger
* SlideToBookButton and will later be made draggable.
*
* @param sliderBtnBackgroundColor Background color for the thumb (distinct from the track)
* @param sliderBtnIcon Icon displayed at the center of the thumb button
*/
@Composable
private fun SliderButton(
sliderBtnWidth: Dp, // Width of the button
sliderBtnBackgroundColor: Color, // Background color for the thumb
@DrawableRes sliderBtnIcon: Int // Icon shown inside the thumb
) {
// Root Box for the slider thumb
Box(
modifier = Modifier
.wrapContentSize()
.width(70.dp)
.height(54.dp)
.background(
color = sliderBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.padding(start = 10.dp, end = 10.dp)
.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(sliderBtnIcon),
contentDescription = "Car Icon",
modifier = Modifier.size(36.dp)
)
}
}
}
Step 3: Add Drag gesture and slide animation

Now that we’ve built the UI for our “Slide to Book” button, let’s start with the animation. We want users to drag the slider thumb from left to right, and once they’ve swiped far enough, we’ll consider it a successful “slide to book” action.

To animate and track the thumb’s position during drag, we’ll need to:

  • Figure out how wide the button is: We use something called Modifier.onSizeChanged to get the actual width of the outer button once it’s displayed. This tells us how far the slider thumb is allowed to move — from the left edge to just before the right edge.
  • Track how far the thumb moves: We use remember { mutableStateOf(… ) } to store the current X-position of the slider thumb in pixels. This allows us to update the thumb’s position reactively as the user drags.
  • Listen for drag gestures: We use Modifier.draggable with rememberDraggableState { delta -> … } to listen for drag gestures in a specified direction (in our case, horizontally). The delta parameter gives us the amount the user’s finger moved since the last update — in pixels, not dp. This means that, every time the user drags their finger left or right, delta tells us how far they moved. We take that delta and add it to our current thumb position, so the thumb follows the finger. If they swipe right, the thumb moves right. If they try to go left again, it moves left — just like dragging a real slider. We also make sure the thumb doesn’t go past the edges. This gives us full control over the drag behavior — not just how far the user can swipe, but when the swipe is “complete”, and even what happens if we want to animate it back later.
  • Finally, we move the thumb visually on the screen: We use Modifier.offset to move a composable by a certain distance on the X (horizontal) or Y (vertical) axis — like nudging it left, right, up, or down. As the user drags their finger, we’re tracking how far they’ve moved using sliderPositionPx (in pixels). We then pass that value to offset — after converting it to dp, since offset expects dp values. This is what makes the slider thumb visually shift across the button, matching the user’s drag. Without offset, even though we’re tracking the finger movement, the thumb would just sit still.

Now let’s update our code:

@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
/**
* ----------------------------------------
* ✨ Step 1:
* • Convert slider button width into pixels so we can use it in math
* • Define a variable to compute the current horizontal position of the slider button (in pixels)
* • Define a variable to capture the total width of the outer button in pixels
* ----------------------------------------
*/
val density = LocalDensity.current
val sliderButtonWidthPx = with(density) { sliderButtonWidthDp.toPx() }
var sliderPositionPx by remember { mutableFloatStateOf(0f) }
var boxWidthPx by remember { mutableIntStateOf(0) }
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
/**
* ----------------------------------------
* ✨ Step 2: Capture the full width of the button once it's laid out
* ----------------------------------------
*/
.onSizeChanged { size ->
boxWidthPx = size.width
}
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp)
/**
* ----------------------------------------
* ✨ Step 3: Shift the slider button based on drag position (px to dp conversion)
* ----------------------------------------
*/
.offset(x = with(density) { sliderPositionPx.toDp() })
/**
* ----------------------------------------
* ✨ Step 4: Handle horizontal drag gestures
* ----------------------------------------
*/
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
// Calculate new potential position
val newPosition = sliderPositionPx + delta
// Clamp it within 0 to (totalWidth - slider button width)
val maxPosition = boxWidthPx - sliderButtonWidthPx
sliderPositionPx = newPosition.coerceIn(0f, maxPosition)
},
onDragStarted = { /* Optional: add feedback or animation here */ },
onDragStopped = {
// TODO: In next step, we’ll trigger onBtnSwipe if drag passes threshold
}
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
}
}

This is what we’re achieved so far:

Step 4: Add text alpha animation

Now that we’ve wired up the drag gesture for the slider button, it’s time to move on to fading out the button text as the slider is dragged. As the user starts sliding: The button label (e.g., “Book Ride ₹199”) gradually fades out. When the slider reaches the end, the text should be completely invisible.

To fade out the text label, we’ll need to:

  • measure how far the slider button has been dragged. That gives us a number from 0.0 (not moved at all) to 1.0 (moved all the way to the end). This value is what we call dragProgress.
  • Next, we define and calculate the textAlpha, which controls how transparent the text is. When dragProgress is 0.0 (user hasn’t moved), textAlpha is 1.0 → the text is fully visible. As the user drags and dragProgress increases (like 0.3, 0.5, 0.7…), we decrease the textAlpha to make the text less visible. When dragProgress reaches 1.0 (fully dragged), textAlpha becomes 0.0 → the text is fully invisible. We apply this textAlpha to our Text composable using Modifier.alpha() — so the text fades smoothly while dragging.
@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
/**
* ----------------------------------------
* ✨ Step 1:
* • Convert slider button width into pixels so we can use it in math
* • Define a variable to compute the current horizontal position of the slider button (in pixels)
* • Define a variable to capture the total width of the outer button in pixels
* ----------------------------------------
*/
val density = LocalDensity.current
val sliderButtonWidthPx = with(density) { sliderButtonWidthDp.toPx() }
var sliderPositionPx by remember { mutableFloatStateOf(0f) }
var boxWidthPx by remember { mutableIntStateOf(0) }
/**
* ----------------------------------------
* ✨ Step 5: Calculate drag progress percentage (0f to 1f)
* ----------------------------------------
*/
val dragProgress = remember(sliderPositionPx, boxWidthPx) {
if (boxWidthPx > 0) {
(sliderPositionPx / (boxWidthPx - sliderButtonWidthPx)).coerceIn(0f, 1f)
} else {
0f
}
}
/**
* ----------------------------------------
* ✨ Step 6: Alpha value for the button label — 1 when untouched, fades to 0 as drag progresses
* ----------------------------------------
*/
val textAlpha = 1f - dragProgress
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
/**
* ----------------------------------------
* ✨ Step 2: Capture the full width of the button once it's laid out
* ----------------------------------------
*/
.onSizeChanged { size ->
boxWidthPx = size.width
}
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
/**
* ----------------------------------------
* ✨ Step 7: Apply the dynamic transparency to the label
* ----------------------------------------
*/
.alpha(textAlpha)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp)
/**
* ----------------------------------------
* ✨ Step 3: Shift the slider button based on drag position (px to dp conversion)
* ----------------------------------------
*/
.offset(x = with(density) { sliderPositionPx.toDp() })
/**
* ----------------------------------------
* ✨ Step 4: Handle horizontal drag gestures
* ----------------------------------------
*/
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
// Calculate new potential position
val newPosition = sliderPositionPx + delta
// Clamp it within 0 to (totalWidth - slider button width)
val maxPosition = boxWidthPx - sliderButtonWidthPx
sliderPositionPx = newPosition.coerceIn(0f, maxPosition)
},
onDragStarted = { /* Optional: add feedback or animation here */ },
onDragStopped = { }
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
}
}

This is what we have so far

Step 5: Add track scale & thumb alpha animation

Once the user slides the button almost to the end, we want two things to happen:

  1. The outer track (button background) should shrink and disappear.
  2. The slider button should fade out completely.

This tells the user that the slide is complete, and the action (like booking the ride) is being processed.

What we need to do:

  • We use a boolean flag sliderComplete that tells us when slide progress has passed a threshold. When dragProgress reaches 80% or more, we set sliderComplete = true.
  • We use animateFloatAsState() and Modifier.graphicsLayer() to smoothly animate the scale and alpha changes.
@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
/**
* ----------------------------------------
* ✨ Step 1:
* • Convert slider button width into pixels so we can use it in math
* • Define a variable to compute the current horizontal position of the slider button (in pixels)
* • Define a variable to capture the total width of the outer button in pixels
* ----------------------------------------
*/
val density = LocalDensity.current
val sliderButtonWidthPx = with(density) { sliderButtonWidthDp.toPx() }
var sliderPositionPx by remember { mutableFloatStateOf(0f) }
var boxWidthPx by remember { mutableIntStateOf(0) }
/**
* ----------------------------------------
* ✨ Step 5: Calculate drag progress percentage (0f to 1f)
* ----------------------------------------
*/
val dragProgress = remember(sliderPositionPx, boxWidthPx) {
if (boxWidthPx > 0) {
(sliderPositionPx / (boxWidthPx - sliderButtonWidthPx)).coerceIn(0f, 1f)
} else {
0f
}
}
/**
* ----------------------------------------
* ✨ Step 6: Alpha value for the button label — 1 when untouched, fades to 0 as drag progresses
* ----------------------------------------
*/
val textAlpha = 1f - dragProgress
/**
* ----------------------------------------
* ✨ Step 8:
* • Add a flag to indicate the slide is complete.
* • Animate the shrinking of the outer button i.e. scale the outer button.
* • Animate the fading of the slider button i.e. make it disappear.
* ----------------------------------------
*/
var sliderComplete by remember { mutableStateOf(false) }
val trackScale by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "trackScale"
)
val sliderAlpha by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "sliderAlpha"
)
/**
* ----------------------------------------
* ✨ Step 9: Mark slide as complete once drag passes 80%.
* This calls the onBtnSwipe method so users can perform whatever action is needed for their app.
* ----------------------------------------
*/
LaunchedEffect(dragProgress) {
if (dragProgress >= 0.8f && !sliderComplete) {
sliderComplete = true
onBtnSwipe()
}
}
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
/**
* ----------------------------------------
* ✨ Step 2: Capture the full width of the button once it's laid out
* ----------------------------------------
*/
.onSizeChanged { size ->
boxWidthPx = size.width
}
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
/**
* ----------------------------------------
* ✨ Step 10: Animate scaling/shrinking of the button
* ----------------------------------------
*/
.graphicsLayer(scaleX = trackScale, scaleY = 1f)
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
/**
* ----------------------------------------
* ✨ Step 7: Apply the dynamic transparency to the label
* ----------------------------------------
*/
.alpha(textAlpha)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp)
/**
* ----------------------------------------
* ✨ Step 3: Shift the slider button based on drag position (px to dp conversion)
* ----------------------------------------
*/
.offset(x = with(density) { sliderPositionPx.toDp() })
/**
* ----------------------------------------
* ✨ Step 4: Handle horizontal drag gestures
* ----------------------------------------
*/
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
// Calculate new potential position
val newPosition = sliderPositionPx + delta
// Clamp it within 0 to (totalWidth - slider button width)
val maxPosition = boxWidthPx - sliderButtonWidthPx
sliderPositionPx = newPosition.coerceIn(0f, maxPosition)
},
onDragStarted = { /* Optional: add feedback or animation here */ },
onDragStopped = { }
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
/**
* ----------------------------------------
* ✨ Step 11: Animate fade out of the slider button
* ----------------------------------------
*/
.alpha(sliderAlpha)
.graphicsLayer { alpha = sliderAlpha }
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
}
}

And this is what we’ec achieved!

Note: The order of modifiers matters here. We have to ensure that we add the .graphicsLayer above the .background here to ensure that the scaling takes place correctly. If we change the order, i.e. if we add the .background after we add the .graphicsayer, the background is already drawn at full size so the scaling doesn’t affect what’s already been rendered. But if you shrink before, everything is drawn inside the new size — and that’s what you want here!

Step 6: Add the loading indicator animation

Now that we have all the moving parts in place, we simply need to show a loading indicator when the animation is complete. For that, we need to:

  • Define a boolean flag showLoading that tells us when we need to show this loading indicator.
  • We need to update our LaunchedEffect(dragProgress) method to set showLoading = true when sliding is completed.
  • We need to display a CircularProgressIndicator() when showLoading = true.

And this is our full code:

/**
*
* This composable creates a custom, swipeable button that resembles a "slide to book" interaction.
* It is composed of two main parts:
* - The outer track (the full-width button background) which displays the label.
* - The inner slider thumb, which can be dragged from left to right.
*
*
* @param btnText Text to display on the outer button track (e.g., "Book Ride ₹199")
* @param btnTextStyle Text style for the button label (e.g., font weight, color)
* @param outerBtnBackgroundColor Background color for the full-width outer button
* @param sliderBtnBackgroundColor Background color for the draggable thumb button
* @param sliderBtnIcon Icon shown inside the slider thumb (e.g., car or arrow icon)
* @param onBtnSwipe Callback triggered once the user slides to complete the booking
*/
@Composable
fun SlideToBookButton(
btnText: String,
btnTextStyle: TextStyle,
outerBtnBackgroundColor: Color,
sliderBtnBackgroundColor: Color,
@DrawableRes sliderBtnIcon: Int,
onBtnSwipe: () -> Unit
) {
// Slider button width
val sliderButtonWidthDp = 70.dp
/**
* ----------------------------------------
* ✨ Step 1:
* • Convert slider button width into pixels so we can use it in math
* • Define a variable to compute the current horizontal position of the slider button (in pixels)
* • Define a variable to capture the total width of the outer button in pixels
* ----------------------------------------
*/
val density = LocalDensity.current
val sliderButtonWidthPx = with(density) { sliderButtonWidthDp.toPx() }
var sliderPositionPx by remember { mutableFloatStateOf(0f) }
var boxWidthPx by remember { mutableIntStateOf(0) }
/**
* ----------------------------------------
* ✨ Step 12: Add a flag that tells us when we need to show this loading indicator
* ----------------------------------------
*/
var showLoadingIndicator by remember { mutableStateOf(false) }
/**
* ----------------------------------------
* ✨ Step 5: Calculate drag progress percentage (0f to 1f)
* ----------------------------------------
*/
val dragProgress = remember(sliderPositionPx, boxWidthPx) {
if (boxWidthPx > 0) {
(sliderPositionPx / (boxWidthPx - sliderButtonWidthPx)).coerceIn(0f, 1f)
} else {
0f
}
}
/**
* ----------------------------------------
* ✨ Step 6: Alpha value for the button label — 1 when untouched, fades to 0 as drag progresses
* ----------------------------------------
*/
val textAlpha = 1f - dragProgress
/**
* ----------------------------------------
* ✨ Step 8:
* • Add a flag to indicate the slide is complete.
* • Animate the shrinking of the outer button i.e. scale the outer button.
* • Animate the fading of the slider button i.e. make it disappear.
* ----------------------------------------
*/
var sliderComplete by remember { mutableStateOf(false) }
val trackScale by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "trackScale"
)
val sliderAlpha by animateFloatAsState(
targetValue = if (sliderComplete) 0f else 1f,
animationSpec = tween(durationMillis = 300), label = "sliderAlpha"
)
/**
* ----------------------------------------
* ✨ Step 9: Mark slide as complete once drag passes 80%.
* This calls the onBtnSwipe method so users can perform whatever action is needed for their app.
* ----------------------------------------
*/
LaunchedEffect(dragProgress) {
if (dragProgress >= 0.8f && !sliderComplete) {
sliderComplete = true
showLoading = true // ✨ Step 13: Update flag when slider is completed
onBtnSwipe()
}
}
// The root layout for the button — stretches full width and has fixed height
Box(
modifier = Modifier
.fillMaxWidth()
.height(55.dp)
/**
* ----------------------------------------
* ✨ Step 2: Capture the full width of the button once it's laid out
* ----------------------------------------
*/
.onSizeChanged { size ->
boxWidthPx = size.width
}
) {
// Outer track — acts as the base of the button
Box(
modifier = Modifier
.matchParentSize()
/**
* ----------------------------------------
* ✨ Step 10: Animate scaling/shrinking of the button
* ----------------------------------------
*/
.graphicsLayer(scaleX = trackScale, scaleY = 1f)
.background(
color = outerBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
)
) {
// The center-aligned button label
Text(
text = btnText,
style = btnTextStyle,
modifier = Modifier.align(Alignment.Center)
/**
* ----------------------------------------
* ✨ Step 7: Apply the dynamic transparency to the label
* ----------------------------------------
*/
.alpha(textAlpha)
)
}
// Slider thumb container, positioned at the left edge of the button
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.padding(1.dp)
/**
* ----------------------------------------
* ✨ Step 3: Shift the slider button based on drag position (px to dp conversion)
* ----------------------------------------
*/
.offset(x = with(density) { sliderPositionPx.toDp() })
/**
* ----------------------------------------
* ✨ Step 4: Handle horizontal drag gestures
* ----------------------------------------
*/
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
// Calculate new potential position
val newPosition = sliderPositionPx + delta
// Clamp it within 0 to (totalWidth - slider button width)
val maxPosition = boxWidthPx - sliderButtonWidthPx
sliderPositionPx = newPosition.coerceIn(0f, maxPosition)
},
onDragStarted = { /* Optional: add feedback or animation here */ },
onDragStopped = {
// TODO: In next step, we’ll trigger onBtnSwipe if drag passes threshold
}
),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
/**
* ----------------------------------------
* ✨ Step 11: Animate fade out of the slider button
* ----------------------------------------
*/
.alpha(sliderAlpha)
.graphicsLayer { alpha = sliderAlpha }
) {
// The draggable thumb itself
SliderButton(
sliderBtnWidth = sliderButtonWidthDp,
sliderBtnBackgroundColor = sliderBtnBackgroundColor,
sliderBtnIcon = sliderBtnIcon
)
}
}
/**
* ----------------------------------------
* ✨ Step 14: Show the loading indicator after the slider reaches the end and the animation completes
* ----------------------------------------
*/
if (showLoading) {
CircularProgressIndicator(
modifier = Modifier.align(Alignment.Center),
color = Color.Black
)
}
}
}
/**
*
* This composable defines the visual appearance of the slider thumb — a small rounded box
* that contains an icon (usually a car or arrow). It is positioned inside the larger
* SlideToBookButton and will later be made draggable.
*
* @param sliderBtnBackgroundColor Background color for the thumb (distinct from the track)
* @param sliderBtnIcon Icon displayed at the center of the thumb button
*/
@Composable
private fun SliderButton(
sliderBtnWidth: Dp, // Width of the button
sliderBtnBackgroundColor: Color, // Background color for the thumb
@DrawableRes sliderBtnIcon: Int // Icon shown inside the thumb
) {
// Root Box for the slider thumb
Box(
modifier = Modifier
.wrapContentSize()
.width(70.dp)
.height(54.dp)
.background(
color = sliderBtnBackgroundColor,
shape = RoundedCornerShape(12.dp)
),
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.padding(start = 10.dp, end = 10.dp)
.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(sliderBtnIcon),
contentDescription = "Car Icon",
modifier = Modifier.size(36.dp)
)
}
}
}

And that’s pretty much it!

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

If you found this useful, feel free to share or reach out with feedback/questions! You can follow more of my Jetpack Compose experiments here:

Happy sliding! 🚜

This article was previously published on proandroiddev.com.

Menu