Skip to content

Instantly share code, notes, and snippets.

@razaghimahdi
Created February 2, 2025 11:15
Show Gist options
  • Save razaghimahdi/a310193f9c0f687da0a072bf2e5cc369 to your computer and use it in GitHub Desktop.
Save razaghimahdi/a310193f9c0f687da0a072bf2e5cc369 to your computer and use it in GitHub Desktop.
Custom Number Spinner
@Composable
fun NumberSpinner(
controller: NumberSpinnerController,
onValueChange: (Int) -> Unit,
) {
LaunchedEffect(controller.value) {
onValueChange(controller.value)
}
NumberSpinnerContent(
color = Color.Blue,
maxValueEnable = controller.maxValueEnable,
minValueEnable = controller.minValueEnable,
value = controller.getValue(),
onNextExecute = {
controller.updateValue(controller.value + 1)
controller.syncMinMax()
},
onPrevExecute = {
controller.updateValue(controller.value - 1)
controller.syncMinMax()
}
)
}
@Preview
@Composable
private fun preview() {
NumberSpinner(rememberNumberSpinner()) {}
}
@SuppressLint("UnrememberedMutableInteractionSource")
@Composable
private fun NumberSpinnerContent(
modifier: Modifier = Modifier,
color: Color,
value: String,
maxValueEnable: Boolean,
minValueEnable: Boolean,
onNextExecute: () -> Unit = {},
onPrevExecute: () -> Unit = {},
) {
val maxValueColor = animateFloatAsState(
targetValue = if (maxValueEnable)
1f
else
.1f,
animationSpec = tween(durationMillis = 300, delayMillis = 0, easing = LinearEasing), label = ""
)
val minValueColor = animateFloatAsState(
targetValue = if (minValueEnable)
1f
else
.1f,
animationSpec = tween(durationMillis = 300, delayMillis = 0, easing = LinearEasing), label = ""
)
Column(
modifier = modifier
.fillMaxWidth()
) {
Row(
modifier = Modifier
.height(55.dp)
.padding(horizontal = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.weight(2f)
.fillMaxHeight()
.clickable(enabled = maxValueEnable, indication = null, interactionSource = MutableInteractionSource()) {
onNextExecute()
}
.border(1.dp, color.copy(alpha = maxValueColor.value), RoundedCornerShape(8.dp)), contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(R.drawable.ic_add),
null,
tint = color.copy(alpha = maxValueColor.value),
modifier = Modifier.size(30.dp)
)
}
Spacer(modifier = Modifier.size(8.dp))
Box(
modifier = Modifier
.weight(6f)
.fillMaxHeight()
.border(1.dp, color, RoundedCornerShape(8.dp)), contentAlignment = Alignment.Center
) {
Text(
text = value,
color = Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 14.sp
)
}
Spacer(modifier = Modifier.size(8.dp))
Box(
modifier = Modifier
.weight(2f)
.fillMaxHeight()
.border(1.dp, color.copy(alpha = minValueColor.value), RoundedCornerShape(8.dp))
.clickable(enabled = minValueEnable, indication = null, interactionSource = MutableInteractionSource()) {
onPrevExecute()
}, contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(R.drawable.ic_reduce),
null,
tint = color.copy(alpha = minValueColor.value),
modifier = Modifier.size(30.dp)
)
}
}
}
}
@Composable
fun rememberNumberSpinner(minValue: Int = 1, maxValue: Int = 12) =
remember(minValue, maxValue) { NumberSpinnerController(minValue = minValue, maxValue = maxValue) }
class NumberSpinnerController() {
constructor(minValue: Int = 1, maxValue: Int = 12) : this() {
updateValue(minValue)
updateMinValue(minValue)
updateMaxValue(maxValue)
updateMinValueEnable(value != minValue && value > minValue)
updateMaxValueEnable(value != maxValue && value < maxValue)
}
private var _minValueEnable: MutableState<Boolean> = mutableStateOf(false)
internal val minValueEnable get() = _minValueEnable.value
private var _maxValueEnable: MutableState<Boolean> = mutableStateOf(false)
internal val maxValueEnable get() = _maxValueEnable.value
private var _minValue: MutableState<Int> = mutableStateOf(1)
private val minValue get() = _minValue.value
private var _maxValue: MutableState<Int> = mutableStateOf(1)
private val maxValue get() = _maxValue.value
private var _value: MutableState<Int> = mutableIntStateOf(1)
internal val value get() = _value.value
internal fun getValue() = value.toString()
private fun updateMinValue(value: Int) {
_minValue.value = value
}
private fun updateMaxValue(value: Int) {
_maxValue.value = value
}
internal fun updateValue(value: Int) {
_value.value = value
}
private fun updateMinValueEnable(value: Boolean) {
_minValueEnable.value = value
}
private fun updateMaxValueEnable(value: Boolean) {
_maxValueEnable.value = value
}
internal fun syncMinMax() {
updateMinValueEnable(value != minValue && value > minValue)
updateMaxValueEnable(value != maxValue && value < maxValue)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment