Skip to content

Instantly share code, notes, and snippets.

@desugar-64
Last active February 10, 2022 19:52
Show Gist options
  • Save desugar-64/6010ebb528d828be3a8e04fa25e810dc to your computer and use it in GitHub Desktop.
Save desugar-64/6010ebb528d828be3a8e04fa25e810dc to your computer and use it in GitHub Desktop.
enum class SwitchState { ON, OFF }
private val animationSpec = spring<IntOffset>(
dampingRatio = Spring.DampingRatioLowBouncy,
stiffness = Spring.StiffnessMedium
)
@OptIn(ExperimentalMaterialApi::class)
@Composable
private fun Switcher(
modifier: Modifier = Modifier,
thumbColor: Color,
thumbShape: Shape,
thumbPadding: Dp,
switchOffContent: @Composable BoxScope.() -> Unit,
switchOnContent: @Composable BoxScope.() -> Unit,
onValueChanged: (Boolean) -> Unit
) {
var thumbWidth: Int by remember { mutableStateOf(0) }
val thumbPaddingPx = with(LocalDensity.current) { thumbPadding.roundToPx() }
val swipeState = rememberSwipeableState(SwitchState.OFF)
val swipeAnchor = remember(thumbWidth, thumbPaddingPx) {
mapOf(
(thumbWidth.toFloat() - thumbPaddingPx * 2) to SwitchState.ON,
0.0f to SwitchState.OFF
)
}
val (isToggled, setter) = remember(swipeState.currentValue) {
mutableStateOf(swipeState.currentValue == SwitchState.ON)
}
val currentValueChangeListener by rememberUpdatedState(onValueChanged)
LaunchedEffect(isToggled) {
swipeState.snapTo(if (isToggled) SwitchState.ON else SwitchState.OFF)
currentValueChangeListener(isToggled)
}
// thumb horizontal position animation
val thumbOffset by animateIntOffsetAsState(
targetValue = IntOffset(
x = swipeState.offset.value.roundToInt(),
y = 0
),
animationSpec = animationSpec
)
Box(
modifier = modifier
.toggleable(
value = isToggled,
onValueChange = setter,
role = Role.Switch,
indication = null,
interactionSource = remember { MutableInteractionSource() }
)
.swipeable(
swipeState,
anchors = swipeAnchor,
orientation = Orientation.Horizontal
)
) {
// Thumb
val widthDp = with(LocalDensity.current) { thumbWidth.toDp() }
Box(
modifier = Modifier
.offset { thumbOffset }
.padding(thumbPadding)
.background(thumbColor, thumbShape)
.fillMaxHeight()
.width(widthDp)
)
// Buttons content
Row(modifier = Modifier.fillMaxSize()) {
Box(
modifier = Modifier
.fillMaxWidth(0.5f)
.fillMaxHeight()
.onSizeChanged { thumbWidth = it.width },
content = { switchOffContent() }
)
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(),
content = { switchOnContent() }
)
}
}
}
// usage
Switcher(
modifier = Modifier
.size(360.dp, 84.dp)
.background(Color.LightGray, RoundedCornerShape(8.dp))
.padding(4.dp),
thumbColor = Color.Cyan,
thumbShape = RoundedCornerShape(4.dp),
thumbPadding = 4.dp,
switchOffContent = {
Text(
modifier = Modifier.align(Alignment.Center),
text = "OFF"
)
},
switchOnContent = {
Text(
modifier = Modifier.align(Alignment.Center),
text = "ON"
)
},
onValueChanged = { /* todo */ }
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment