En este tutorial estudiaremos los indicadores de progreso en Compose, para representar el inicio de una tarea que tomará un tiempo indefinido, o bien, una duración con el porcentaje avanzado.
Encontrarás varios ejemplos sobre la creación de indicadores lineales y circulares como se muestra en la anterior imagen. Dirígete a ellos por la siguiente lista de contenidos:
Indicadores De Progreso En Compose
Al igual que los demás apartados de la guía de Jetpack Compose, el código para los indicadores de progreso está disponible en el repositorio de GitHub:
Localiza las funciones componibles en el paquete examples/ProgressIndicadors
del módulo :p7_componentes
. Recuerda apoyar el repositorio con un Star.
1. Indicador De Progreso Lineal
Para crear una indicador de progreso lineal usa la función componible LinearProgressIndicador()
en cualquiera de sus dos versiones (determinado e indeterminado):
// Determinado
@Composable
fun LinearProgressIndicator(
progress: Float?,
modifier: Modifier? = Modifier,
color: Color? = MaterialTheme.colors.primary,
backgroundColor: Color? = color.copy(alpha = IndicatorBackgroundOpacity)
): Unit
// Inderterminado
@Composable
fun LinearProgressIndicator(
modifier: Modifier? = Modifier,
color: Color? = MaterialTheme.colors.primary,
backgroundColor: Color? = color.copy(alpha = IndicatorBackgroundOpacity)
): Unit
Donde sus parámetros representan los siguientes atributos:
progress
: La cantidad de progreso del indicador. Su rango de valores es [0.0, 1.0]color
: Color del indicador de progresobackgroundColor
: Color de la pista del indicador
Veamos algunos ejemplos.
1.1 LinearProgressIndicator Determinado
Supongamos que deseamos representar el proceso de sincronización de datos del usuario, a través de un indicador lineal como se muestra en la figura 2.
En principio solo debemos declarar el estado inicial y pasarlo al atributo progress
del componente:
@Composable
fun DeterminedLinearProgress() {
var progress by remember { mutableStateOf(0.0f) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(all = 16.dp)
) {
Text(text = "Sincronizando datos")
Spacer(Modifier.height(16.dp))
LinearProgressIndicator(
progress = animatedProgress,
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 16.dp)
)
}
}
Ahora bien, si queremos simular el avance del progreso, podemos ejecutar una corrutina con LaunchedEffect
. Esta se encargará de incrementar el estado del progreso en 10 puntos porcentuales en en intervalos de 300 milisegundos:
@Composable
fun DeterminedLinearProgress() {
var progress by remember { mutableStateOf(0.0f) }
LaunchedEffect(true) { // Incrementos
for (i in 0..100 step 10) {
delay(300)
if (i == 100) {
cancel()
}
progress = i / 100f
}
}
val animatedProgress by animateFloatAsState( // Animación de progreso
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(all = 16.dp)
) {
Text(text = "Sincronizando datos")
Spacer(Modifier.height(16.dp))
LinearProgressIndicator(
progress = animatedProgress,
modifier = Modifier
.wrapContentHeight()
.padding(horizontal = 16.dp)
)
}
}
Adicionalmente, animamos el progreso entre cada valor con animateFloatAsState()
con el fin de visualizar un avance más fluido.
De esta forma, al ejecutar o entrar al modo interacción, verás la siguiente animación del progreso:
1.2 LinearProgressIndicator Indeterminado
Tomemos como ejemplo un escaneo sobre la cantidad de medidores de electricidad que existen en un sector. Debido a que la cantidad varía de sector a sector, el progreso será indefinido como se muestra en la figura 3.
A fin de implementar este indicador de progreso lineal indeterminado, invocamos a la función sin el parámetro progress
:
@Composable
fun UndeterminedLinearProgress() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(all = 16.dp)
) {
Text(text = "Escaneando medidores")
Spacer(Modifier.height(16.dp))
LinearProgressIndicator()
}
}
Al previsualizar la función UndeterminedLinearProgress()
verás el indicador animado entre la pista hasta que se llegue al fin de la tarea:
1.3 Cambiar Color Del Indicador De Progreso
Usa los parámetros color
y backgroundColor
para modificar el color del indicador y la pista respectivamente.
Por ejemplo, usemos Azul 500 para el progreso y el gris claro Color.LightGray
en un indicador lineal indeterminado:
@Composable
fun ColoredLinearProgress() {
val blue500 = Color(0xFF2196F3)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(16.dp)
) {
LinearProgressIndicator(
color = blue500,
backgroundColor = Color.LightGray
)
}
}
El efecto del cambio de color es:
2. Indicador De Progreso Circular
En el caso de los indicadores circulares, invoca a la función CircularProgressIndicador()
para crear y mostrarlos en pantalla. Al igual que los lineares, esta función viene en dos versiones para determinados e indeterminados:
// Determinado
@Composable
fun CircularProgressIndicator(
progress: Float?,
modifier: Modifier? = Modifier,
color: Color? = MaterialTheme.colors.primary,
strokeWidth: Dp? = ProgressIndicatorDefaults.StrokeWidth
): Unit
// Indeterminado
@Composable
fun CircularProgressIndicator(
modifier: Modifier? = Modifier,
color: Color? = MaterialTheme.colors.primary,
strokeWidth: Dp? = ProgressIndicatorDefaults.StrokeWidth
): Unit
Donde:
progress
: Es el progreso que representa el recorrido circular con valores [0.0,1.0] (representación de 0 a 360 grados)color
: Es el color del indicadorstrokeWidth
: Es el ancho del indicador
2.1 CircularProgressIndicator Determinado
Mostremos en pantalla un indicador circular determinado cuyo fin es reflejar el estado de la subida de un archivo a la nube, como se ve en la imagen anterior.
La solución es exactamente igual a como vimos en el indicador de progreso lineal determinado. Ejecutamos una corrutina y añadimos la animación entre valores de progreso:
@Composable
fun DeterminedCircularProgress(progress: Float) {
val percentage: Int = (progress * 100).toInt()
Box(contentAlignment = Alignment.Center) {
Text(text = "$percentage%", style = MaterialTheme.typography.body2)
CircularProgressIndicator(
progress = progress,
modifier = Modifier.size(70.dp)
)
}
}
@Composable
fun UploadFileView() {
var progress by remember { mutableStateOf(0.0f) }
LaunchedEffect(true) {
for (i in 0..100 step 10) {
delay(300)
if (i == 100) {
cancel()
}
progress = i / 100f
}
}
val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
Column(
Modifier
.padding(all = 16.dp)
.fillMaxSize()
.wrapContentHeight()
.border(width = 1.dp, color = Color.LightGray, shape = RoundedCornerShape(4.dp))
.padding(all = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
DeterminedCircularProgress(progress = animatedProgress)
Spacer(Modifier.height(16.dp))
Text(text = "Subiendo archivo...")
Spacer(Modifier.height(8.dp))
OutlinedButton(onClick = { }) {
Text(text = "Cancelar")
}
}
}
Hemos elevado el estado del indicador de progreso al crear la función DeterminedCircularProgress()
, la cual recibe el valor del progreso. Esta es llamada desde UploadFileView()
donde se crea el diseño junto a un texto y botón de cancelar.
El resultado final es:
2.2 CircularProgressIndicator Indeterminado
Al igual que el indicador lineal indeterminado, el circular no recibe parámetro progress
. Basta con invocar a CircularProgressIndicator
sin más.
@Composable
fun UndeterminedCircularProgress() {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
CircularProgressIndicator()
Spacer(Modifier.height(16.dp))
Text(text = "Cargando")
}
}
Al materializarlo tenemos:
2.3 Cambiar Color Del Indicador De Progreso
Ya para finalizar, cambiemos el color del indicador circular anterior por amarillo y azul. La idea es crear una animación (todo) que intercambie cada 200 milisegundos entre estos dos valores.
El código de solución es:
@Composable
fun ColoredCircularIndicator() {
val infiniteTransition = rememberInfiniteTransition()
val color by infiniteTransition.animateColor(
initialValue = Color.Blue,
targetValue = Color.Yellow,
animationSpec = infiniteRepeatable(
animation = tween(200, easing = LinearEasing),
repeatMode = RepeatMode.Reverse
)
)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
) {
CircularProgressIndicator(color = color)
Spacer(Modifier.height(16.dp))
Text(text = "Cargando")
}
}
Como ves, creamos una transición infinita entre Color.Blue
y Color.Yellow
por medio de animateColor()
. Luego aplicamos el valor del estado color sobre el parámetro del CircularProgressIndicator
.
El resultado de la animación es:
Únete Al Discord De Develou
Si tienes problemas con el código de este tutorial, preguntas, recomendaciones o solo deseas discutir sobre desarrollo Android conmigo y otros desarrolladores, únete a la comunidad de Discord de Develou y siéntete libre de participar como gustes. ¡Te espero!