Skip to content

Instantly share code, notes, and snippets.

@farshadrezaee
Created May 19, 2023 04:52
Show Gist options
  • Save farshadrezaee/191e1cd1f3d3890c300c06988d58f34c to your computer and use it in GitHub Desktop.
Save farshadrezaee/191e1cd1f3d3890c300c06988d58f34c to your computer and use it in GitHub Desktop.
Stepper with Jetpack Compose.
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Done
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun Stepper(
numberOfSteps: Int,
currentStep: Int,
onStepChanged: (step: Int) -> Unit,
modifier: Modifier = Modifier,
stepDescriptionList: List<String> = List(numberOfSteps) { "Step ${it + 1}" },
unSelectedColor: Color = LightGray,
selectedColor: Color = Primary,
canChangeStep: (numberOfSteps: Int, currentStep: Int) -> Boolean = { _, _ -> true }
) {
Row(
modifier = modifier
.horizontalScroll(rememberScrollState()),
verticalAlignment = Alignment.CenterVertically
) {
val currentStepState = rememberSaveable { mutableStateOf(currentStep) }
for (step in 1..numberOfSteps) {
Step(
step = step,
isCompleted = step < currentStepState.value,
isCurrent = step == currentStepState.value,
isLast = step == numberOfSteps,
stepDescription = stepDescriptionList[step - 1],
unSelectedColor = unSelectedColor,
selectedColor = selectedColor,
onClick = {
if (canChangeStep.invoke(numberOfSteps, it)) {
currentStepState.value = it
onStepChanged.invoke(it)
}
},
)
}
}
}
@Composable
private fun Step(
step: Int,
isCompleted: Boolean,
isCurrent: Boolean,
isLast: Boolean,
stepDescription: String,
unSelectedColor: Color,
selectedColor: Color,
onClick: (step: Int) -> Unit,
modifier: Modifier = Modifier,
fontSize: TextUnit = 10.sp
) {
val color: Color = if (isCurrent || isCompleted) {
selectedColor
} else {
unSelectedColor
}
val dividerColor: Color = if (isCompleted) {
selectedColor
} else {
unSelectedColor
}
Column(modifier = modifier) {
Row {
if (isCompleted) {
DoneIcon(
backgroundColor = color,
modifier = Modifier.noRippleClickable {
if (isCurrent.not()) {
onClick.invoke(step)
}
}
)
} else {
StepText(
text = step.toString(),
backgroundColor = color,
modifier = Modifier.noRippleClickable {
if (isCurrent.not()) {
onClick.invoke(step)
}
},
fontSize = fontSize
)
}
if (!isLast) {
Divider(
modifier = Modifier
.align(alignment = Alignment.CenterVertically)
.padding(horizontal = 8.dp)
.width(40.dp),
color = dividerColor,
thickness = 1.dp,
)
}
}
DescriptionText(
text = stepDescription,
color = color,
fontSize = fontSize,
modifier = Modifier.noRippleClickable {
if (isCurrent.not()) {
onClick.invoke(step)
}
}
)
}
}
@Composable
private fun StepText(
text: String,
backgroundColor: Color,
modifier: Modifier,
fontSize: TextUnit
) {
Text(
modifier = modifier
.background(
color = backgroundColor,
shape = CircleShape
)
.size(
width = 24.dp,
height = 24.dp
)
.wrapContentHeight(Alignment.CenterVertically),
textAlign = TextAlign.Center,
text = text,
color = White,
fontSize = fontSize,
)
}
@Composable
private fun DoneIcon(
backgroundColor: Color,
modifier: Modifier
) {
Icon(
imageVector = Icons.Default.Done, "done",
modifier = modifier
.background(
color = backgroundColor,
shape = CircleShape
)
.size(
width = 24.dp,
height = 24.dp
)
.padding(4.dp),
tint = White
)
}
@Composable
private fun DescriptionText(
text: String,
color: Color,
fontSize: TextUnit,
modifier: Modifier = Modifier
) {
Text(
modifier = modifier,
fontSize = fontSize,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
text = text,
color = color,
)
}
fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = composed {
clickable(indication = null,
interactionSource = remember { MutableInteractionSource() }) {
onClick()
}
}
@farshadrezaee
Copy link
Author

stepper

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment