Last active
March 21, 2023 09:35
-
-
Save KlassenKonstantin/788962b8dbe357ec0bb7ca311626f5b3 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
// Usage | |
// PingPongSwitch( | |
// textFirst = "Ping", | |
// textSecond = "Pong" | |
// ) | |
@Composable | |
fun PingPongSwitch( | |
state: PingPongSwitchState = rememberPingPongSwitchState(), | |
textFirst: String, | |
textSecond: String, | |
) { | |
Box( | |
modifier = Modifier | |
.pingPongSwitchPointer(state) | |
.graphicsLayer { | |
cameraDistance = 16f | |
rotationY = state.tiltProgress.value * 16 | |
}, | |
) { | |
Box( | |
modifier = Modifier | |
.width(IntrinsicSize.Max) | |
) { | |
Row( | |
Modifier | |
.width(300.dp) | |
.border(1.dp, color, RoundedCornerShape(50)) | |
.clip(RoundedCornerShape(50)) | |
) { | |
Text( | |
modifier = Modifier | |
.padding(vertical = 12.dp) | |
.weight(1f), text = textFirst, color = color, textAlign = TextAlign.Center, | |
style = MaterialTheme.typography.bodyLarge | |
) | |
Text( | |
modifier = Modifier | |
.padding(vertical = 12.dp) | |
.weight(1f), | |
text = textSecond, color = color, textAlign = TextAlign.Center | |
) | |
} | |
Box(modifier = Modifier | |
.graphicsLayer { | |
cameraDistance = 16f | |
transformOrigin = TransformOrigin(1f, 0.5f) | |
rotationY = state.flipRotation.value | |
} | |
.background(color, RoundedCornerShape(CornerSize(50), CornerSize(0), CornerSize(0), CornerSize(50))) | |
.fillMaxWidth(0.5f), | |
contentAlignment = Alignment.Center | |
) { | |
Text( | |
modifier = Modifier | |
.padding(vertical = 12.dp) | |
.graphicsLayer { | |
rotationY = if (state.flipRotation.value <= 90f) 0f else 180f | |
}, | |
text = if (state.flipRotation.value <= 90f) textFirst else textSecond, | |
color = Color.White, | |
textAlign = TextAlign.Center | |
) | |
} | |
} | |
} | |
} | |
fun Modifier.pingPongSwitchPointer(state: PingPongSwitchState) = pointerInput(Unit) { | |
forEachGesture { | |
awaitPointerEventScope { | |
do { | |
val event = awaitPointerEvent() | |
val xRelative = event.changes.first().position.x / size.width | |
state.setTouchTarget(if (xRelative <= 0.5f) PingPongSwitchState.TouchTarget.FIRST else PingPongSwitchState.TouchTarget.SECOND) | |
} while (event.type != PointerEventType.Release) | |
state.setTouchTarget(PingPongSwitchState.TouchTarget.NONE) | |
} | |
} | |
} | |
@Composable | |
fun rememberPingPongSwitchState( | |
initialSelection: PingPongSwitchState.Selection = PingPongSwitchState.Selection.FIRST, | |
): PingPongSwitchState { | |
val scope = rememberCoroutineScope() | |
return remember(initialSelection) { PingPongSwitchState(initialSelection, scope) } | |
} | |
class PingPongSwitchState( | |
initialSelection: Selection, | |
private val scope: CoroutineScope | |
) { | |
val selection = mutableStateOf(initialSelection) | |
val tiltProgress = Animatable(0f) | |
val flipRotation = Animatable(0f) | |
init { | |
scope.launch { | |
setSelection(initialSelection, false) | |
} | |
} | |
fun setSelection(selection: Selection, animate: Boolean) { | |
this.selection.value = selection | |
val targetRotation = if (selection == Selection.FIRST) 0f else 180f | |
scope.launch { | |
if (animate) { | |
flipRotation.animateTo( | |
targetRotation, | |
spring(dampingRatio = Spring.DampingRatioNoBouncy, stiffness = Spring.StiffnessLow) | |
) | |
} else { | |
flipRotation.snapTo(targetRotation) | |
} | |
} | |
} | |
fun setTouchTarget(touchTarget: TouchTarget) { | |
scope.launch { | |
tiltProgress.animateTo( | |
when (touchTarget) { | |
TouchTarget.FIRST -> -1f | |
TouchTarget.SECOND -> 1f | |
TouchTarget.NONE -> 0f | |
}, | |
spring(dampingRatio = Spring.DampingRatioMediumBouncy, stiffness = Spring.StiffnessLow) | |
) | |
} | |
when (touchTarget) { | |
TouchTarget.FIRST -> setSelection(Selection.FIRST, true) | |
TouchTarget.SECOND -> setSelection(Selection.SECOND, true) | |
TouchTarget.NONE -> {} | |
} | |
} | |
enum class Selection { | |
FIRST, SECOND | |
} | |
enum class TouchTarget { | |
FIRST, SECOND, NONE | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment