Skip to content

Instantly share code, notes, and snippets.

@mapm14
Created July 12, 2022 16:41
Show Gist options
  • Save mapm14/66a18a3c75229dff2d7de746446bd102 to your computer and use it in GitHub Desktop.
Save mapm14/66a18a3c75229dff2d7de746446bd102 to your computer and use it in GitHub Desktop.
BouncyRopes Composable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateOffsetAsState
import androidx.compose.animation.core.spring
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.requiredSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlin.math.abs
import kotlin.math.roundToInt
@Composable
fun BouncyRopes() {
val offsetDot1 = remember { mutableStateOf(Offset(200f, 300f)) }
val offsetDot2 = remember { mutableStateOf(Offset(400f, 300f)) }
val offsetDot3 = remember { mutableStateOf(Offset(600f, 300f)) }
val offsetDot4 = remember { mutableStateOf(Offset(800f, 300f)) }
val dotSize = 16.dp
Box {
Dot(
size = dotSize,
offset = offsetDot1,
)
Dot(
size = dotSize,
offset = offsetDot2,
)
Dot(
size = dotSize,
offset = offsetDot3,
)
Dot(
size = dotSize,
offset = offsetDot4,
)
}
Line(
dotsSizeInDp = dotSize,
offsetDotA = offsetDot1,
offsetDotB = offsetDot2,
)
Line(
dotsSizeInDp = dotSize,
offsetDotA = offsetDot2,
offsetDotB = offsetDot3,
)
Line(
dotsSizeInDp = dotSize,
offsetDotA = offsetDot3,
offsetDotB = offsetDot4,
)
}
@Composable
private fun Line(
dotsSizeInDp: Dp,
offsetDotA: MutableState<Offset>,
offsetDotB: MutableState<Offset>,
) {
fun MutableState<Offset>.getDotOffset() = Offset(
x = value.x + dotsSizeInDp.value,
y = value.y + dotsSizeInDp.value,
)
val path by remember { mutableStateOf(Path()) }
val horizontalDelta by remember { derivedStateOf { abs(offsetDotA.value.x - offsetDotB.value.x) / 2 } }
val midAnimatedPointOffset = animateOffsetAsState(
targetValue = Offset(
x = minOf(offsetDotA.value.x, offsetDotB.value.x) + horizontalDelta,
y = maxOf(offsetDotA.value.y, offsetDotB.value.y) + horizontalDelta,
),
animationSpec = spring(dampingRatio = Spring.DampingRatioHighBouncy, stiffness = 105f),
)
Canvas(modifier = Modifier.fillMaxSize()) {
path.reset()
path.moveTo(x = offsetDotA.getDotOffset().x, y = offsetDotA.getDotOffset().y)
path.quadraticBezierTo(
x1 = midAnimatedPointOffset.value.x,
y1 = midAnimatedPointOffset.value.y,
x2 = offsetDotB.getDotOffset().x,
y2 = offsetDotB.getDotOffset().y,
)
drawPath(
path = path,
color = Color.White,
style = Stroke(width = 5f),
)
}
}
@Composable
fun Dot(size: Dp, offset: MutableState<Offset>) {
Box(
modifier = Modifier
.offset { IntOffset(offset.value.x.roundToInt(), offset.value.y.roundToInt()) }
.requiredSize(size)
.border(BorderStroke(size, Color.White), CircleShape)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consumeAllChanges()
offset.value += dragAmount
}
}
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment