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.
- Shared State Logic: Separate your gesture area from your display area.
- Physics-Based: Built on top of
androidx.compose.animation.core.springfor that natural, non-linear feel. - Three-Way Crossfade: Smoothly transition between
start,center, andendlabels based on drag intensity. - Customizable Resistance: Control how hard your users have to "pull" to see the hidden labels.
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:
- Initialize the State
Create a rememberRubberDragState. You can tweak the dampingRatio and stiffness here to make it
feel "tight" or "loose."
- Wrap your content in a DragBox
Place a RubberDragBox in your layout. This defines the area where the user can actually drag.
- Add the TextView
Inside (or even outside) that box, add the RubberTextView and pass it your labels.
- Sit back and stretch
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!
