Instantly share code, notes, and snippets.
Last active
April 8, 2025 09:35
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save anitaa1990/bd3d257337317cb71fdfd8ca73188bfb to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@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 | |
) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment