
Tactile rubber-band drag interaction for text labels, with physics-based spring animation, shared drag/display state, three-way crossfade, configurable resistance and bounce.
Give your Compose UI some snap. Why settle for static text when you can have text that
stretches? RubberTextView is a lightweight Jetpack Compose library that brings a tactile,
rubber-band interaction to your labels. Drag to reveal new values, let go to watch them spring back.
It’s perfect for value pickers, playful navigation, or just giving your users something satisfying to fidget with.
androidx.compose.animation.core.spring for that natural,
non-linear feel.start, center, and end labels based on
drag intensity.Add the dependency to your build.gradle.kts:
implementation("com.dontsaybojio:rubbertextview:X.X.X")Make sure mavenCentral() is in your repository list:
repositories {
mavenCentral()
}RubberTextView works by splitting the interaction into three simple parts:
RubberDragState: The brain. It tracks the offset and handles the spring physics.RubberDragBox: The muscle. This is the invisible (or visible!) surface that catches the
user's thumb.RubberTextView: The face. This renders your labels and handles the crossfading magic.Getting that "squishy" feel into your app is a four-step process:
Create a rememberRubberDragState. You can tweak the dampingRatio and stiffness here to make it
feel "tight" or "loose."
Place a RubberDragBox in your layout. This defines the area where the user can actually drag.
Inside (or even outside) that box, add the RubberTextView and pass it your labels.
Run your app and enjoy the springy goodness.
@Composable
fun MySpringyUI() {
// 1. Create the state
val state = rememberRubberDragState(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
Column {
// 2. The Display
RubberTextView(
modifier = Modifier.fillMaxWidth().padding(20.dp),
startLabel = "Less",
label = "Just Right",
endLabel = "More",
state = state
)
// 3. The Interactive Surface
RubberDragBox(
modifier = Modifier.fillMaxWidth().height(200.dp).background(Color.LightGray),
state = state,
onDragCompleted = { direction ->
println("Drag completed: $direction") // RubberDragDirection.Left or .Right
}
) {
Text("Drag me horizontally!", Modifier.align(Alignment.Center))
}
}
}You have full control over the "tension" of the rubber band:
| Parameter | Description | Values | Default |
|---|---|---|---|
resistance |
How much the drag "resists" the finger. | Float | 0.4f |
dampingRatio |
Controls the bounciness (e.g., Bouncy vs NoBouncy). |
DampingRatioHighBouncy, DampingRatioMediumBouncy, DampingRatioLowBouncy, DampingRatioNoBouncy | DampingRatioMediumBouncy |
stiffness |
How fast the text snaps back to the center. | StiffnessHigh, StiffnessMedium, StiffnessMediumLow, StiffnessLow, StiffnessVeryLow | StiffnessMedium |
| Parameter | Description | Values | Default |
|---|---|---|---|
fadeThresholdDivisor |
Adjusts how quickly the side labels fade in during a drag. | Float | 2.5f |
| Parameter | Description | Values | Default |
|---|---|---|---|
enabled |
When false, the horizontal drag gesture is disabled and onDragCompleted is never invoked. Content is still rendered normally. |
Boolean | true |
onDragCompleted |
Optional callback invoked when the drag ends and the offset exceeds dragThreshold. Receives RubberDragDirection.Left or RubberDragDirection.Right. Not called below threshold. |
((RubberDragDirection) -> Unit)? |
null |
dragThreshold |
Minimum absolute offset that must be reached for onDragCompleted to fire. |
Float | 85f |
Got an idea to make it even stretchier? Maybe vertical dragging or multi-label support? Pull requests are always welcome!
Give your Compose UI some snap. Why settle for static text when you can have text that
stretches? RubberTextView is a lightweight Jetpack Compose library that brings a tactile,
rubber-band interaction to your labels. Drag to reveal new values, let go to watch them spring back.
It’s perfect for value pickers, playful navigation, or just giving your users something satisfying to fidget with.
androidx.compose.animation.core.spring for that natural,
non-linear feel.start, center, and end labels based on
drag intensity.Add the dependency to your build.gradle.kts:
implementation("com.dontsaybojio:rubbertextview:X.X.X")Make sure mavenCentral() is in your repository list:
repositories {
mavenCentral()
}RubberTextView works by splitting the interaction into three simple parts:
RubberDragState: The brain. It tracks the offset and handles the spring physics.RubberDragBox: The muscle. This is the invisible (or visible!) surface that catches the
user's thumb.RubberTextView: The face. This renders your labels and handles the crossfading magic.Getting that "squishy" feel into your app is a four-step process:
Create a rememberRubberDragState. You can tweak the dampingRatio and stiffness here to make it
feel "tight" or "loose."
Place a RubberDragBox in your layout. This defines the area where the user can actually drag.
Inside (or even outside) that box, add the RubberTextView and pass it your labels.
Run your app and enjoy the springy goodness.
@Composable
fun MySpringyUI() {
// 1. Create the state
val state = rememberRubberDragState(
dampingRatio = Spring.DampingRatioMediumBouncy,
stiffness = Spring.StiffnessLow
)
Column {
// 2. The Display
RubberTextView(
modifier = Modifier.fillMaxWidth().padding(20.dp),
startLabel = "Less",
label = "Just Right",
endLabel = "More",
state = state
)
// 3. The Interactive Surface
RubberDragBox(
modifier = Modifier.fillMaxWidth().height(200.dp).background(Color.LightGray),
state = state,
onDragCompleted = { direction ->
println("Drag completed: $direction") // RubberDragDirection.Left or .Right
}
) {
Text("Drag me horizontally!", Modifier.align(Alignment.Center))
}
}
}You have full control over the "tension" of the rubber band:
| Parameter | Description | Values | Default |
|---|---|---|---|
resistance |
How much the drag "resists" the finger. | Float | 0.4f |
dampingRatio |
Controls the bounciness (e.g., Bouncy vs NoBouncy). |
DampingRatioHighBouncy, DampingRatioMediumBouncy, DampingRatioLowBouncy, DampingRatioNoBouncy | DampingRatioMediumBouncy |
stiffness |
How fast the text snaps back to the center. | StiffnessHigh, StiffnessMedium, StiffnessMediumLow, StiffnessLow, StiffnessVeryLow | StiffnessMedium |
| Parameter | Description | Values | Default |
|---|---|---|---|
fadeThresholdDivisor |
Adjusts how quickly the side labels fade in during a drag. | Float | 2.5f |
| Parameter | Description | Values | Default |
|---|---|---|---|
enabled |
When false, the horizontal drag gesture is disabled and onDragCompleted is never invoked. Content is still rendered normally. |
Boolean | true |
onDragCompleted |
Optional callback invoked when the drag ends and the offset exceeds dragThreshold. Receives RubberDragDirection.Left or RubberDragDirection.Right. Not called below threshold. |
((RubberDragDirection) -> Unit)? |
null |
dragThreshold |
Minimum absolute offset that must be reached for onDragCompleted to fire. |
Float | 85f |
Got an idea to make it even stretchier? Maybe vertical dragging or multi-label support? Pull requests are always welcome!