¡Hola! Espero que estés teniendo un buen día. el día de hoy vamos a crear un Swipe Button (Botón deslizar) usando Jetpack Compose, sin mas nada que comencemos desarrollar.
@Composable
fun SwipeIndicator(
modifier: Modifier = Modifier,
backgroundColor: Color,
) {
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.fillMaxHeight()
.padding(2.dp)
.clip(CircleShape)
.aspectRatio(
ratio = 1.0F,
matchHeightConstraintsFirst = true,
)
.background(Color.White),
) {
Icon(
imageVector = Icons.Rounded.ChevronRight,
contentDescription = null,
tint = backgroundColor,
modifier = Modifier.size(36.dp),
)
}
}
El swipeable Modifier
lo usaremos para el manejo de la acción de deslizar.
swipeableState.currentValue = 0
significa que el botón está en su posición inicial y swipeableState.currentValue = 1
significa que se completó la acción de deslizar.
Usaremos esta lógica junto con LaunchedEffect
para determinar el deslizamiento completo.
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SwipeButton(
text: String,
isComplete: Boolean,
doneImageVector: ImageVector = Icons.Rounded.Done,
modifier: Modifier = Modifier,
backgroundColor: Color = Color(0xFF03A9F4),
onSwipe: () -> Unit,
) {
val width = 200.dp
val widthInPx = with(LocalDensity.current) {
width.toPx()
}
val anchors = mapOf(
0F to 0,
widthInPx to 1,
)
val swipeableState = rememberSwipeableState(0)
val (swipeComplete, setSwipeComplete) = remember {
mutableStateOf(false)
}
val alpha: Float by animateFloatAsState(
targetValue = if (swipeComplete) {
0F
} else {
1F
},
animationSpec = tween(
durationMillis = 300,
easing = LinearEasing,
)
)
LaunchedEffect(
key1 = swipeableState.currentValue,
) {
if (swipeableState.currentValue == 1) {
setSwipeComplete(true)
onSwipe()
}
}
Box(
contentAlignment = Alignment.Center,
modifier = modifier
.padding(
horizontal = 48.dp,
vertical = 16.dp,
)
.clip(CircleShape)
.background(backgroundColor)
.animateContentSize()
.then(
if (swipeComplete) {
Modifier.width(64.dp)
} else {
Modifier.fillMaxWidth()
}
)
.requiredHeight(64.dp),
) {
SwipeIndicator(
modifier = Modifier
.align(Alignment.CenterStart)
.alpha(alpha)
.offset {
IntOffset(swipeableState.offset.value.roundToInt(), 0)
}
.swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { _, _ ->
FractionalThreshold(0.3F)
},
orientation = Orientation.Horizontal,
),
backgroundColor = backgroundColor,
)
Text(
text = text,
color = White,
fontWeight = FontWeight.Bold,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.alpha(alpha)
.padding(
horizontal = 80.dp,
)
.offset {
IntOffset(swipeableState.offset.value.roundToInt(), 0)
},
)
AnimatedVisibility(
visible = swipeComplete && !isComplete,
) {
CircularProgressIndicator(
color = White,
strokeWidth = 1.dp,
modifier = Modifier
.fillMaxSize()
.padding(4.dp),
)
}
AnimatedVisibility(
visible = isComplete,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
imageVector = doneImageVector,
contentDescription = null,
tint = White,
modifier = Modifier
.align(Alignment.Center)
.size(44.dp),
)
}
}
}
he usado FractionalThreshold
de 0.3
. Esto significa que si deslizamos menos del 30% del ancho, el indicador volverá al estado inicial. Si desliza más que ese umbral, el indicador se ajusta a la posición final.
Y finalmente, un ejemplo para demostrar cómo usar el botón.
@Composable
fun SwipeButtonSample() {
val coroutineScope = rememberCoroutineScope()
val (isComplete, setIsComplete) = remember {
mutableStateOf(false)
}
SwipeButton(
text = "SAVE",
isComplete = isComplete,
onSwipe = {
coroutineScope.launch {
delay(2000)
setIsComplete(true)
}
},
)
}
Podemos usar el onSwipe
controlador para realizar llamadas de red, operaciones de base de datos o cualquier otra acción requerida.
se usa para simular cualquier operación de este delay
tipo que estaríamos usando en la aplicación real.
Fuente: medium.com