Índice de contenido
- Progress Indicators: Linear y Circular con Jetpack Compose
- 1. Circular Progress Indicator: Progreso Indeterminado
- Circular Progress Indicator Legacy
- 2. Linear Progress Indicator: Progreso determinado
- CircularProgressIndicator y LinearProgressIndicator
- Personalización y Detalles Visuales
- Forma Legacy (Sistema de Vistas XML)
- Formas de uso de la ProgressBar
- 1. ProgressBar determinada
- 2. ProgressBar indeterminada
- Caso práctico
En esta entrada hablaremos sobre otro método de para indicar el progreso de carga de una operación que puede sustituir al ya desactualizado (deprecated) por las nuevas versiones de Android ProgressDialog ; lo que significa que en futuras versiones de Android dejaría de existir este componente de software.
El elemento de interfaz de usuario en cuestión luce de la siguiente forma:

Y se conoce como ProgressBar; como puedes ver es una especie de barra lateral animada la cual ya ha estado presente en sitios web como YouTube o en la misma aplicación para Android de YouTube entre otras aplicaciones y en Android moderno tenemos varias variantes como el de tipo Circular o el de tipo Linear.
Nos quedamos en como conocemos como usar los menús Dropdown de opciones en Android Studio.
Progress Indicators: Linear y Circular con Jetpack Compose
Con la llegada de Jetpack Compose, la forma de construir interfaces de usuario en Android ha cambiado a un enfoque declarativo. En lugar de XML, ahora usamos funciones Composable escritas en Kotlin.
En Compose, los indicadores de progreso se llaman CircularProgressIndicator (para el estilo de círculo) y LinearProgressIndicator (para el estilo de barra horizontal).

1. Circular Progress Indicator: Progreso Indeterminado
Se usa cuando no se conoce el tiempo que tardará una operación.
@Composable
fun MyLoadingScreen() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator()
}
}Circular Progress Indicator Legacy
Funciona de manera idéntica al anterior, pero con una forma circular. Aunque puedes pasarle el valor directamente, se recomienda el uso de llaves {} para optimizar las recomposiciones mediante el uso de las funciones lambdas.
CircularProgressIndicator(
progress = { currentProgress }
)
CircularProgressIndicator(progress = currentProgress) // legacy2. Linear Progress Indicator: Progreso determinado
Es el que ocupa el ancho de la pantalla. Podemos personalizar su tamaño y grosor mediante un Modifier.
Para mostrarlo u ocultarlo, puedes usar una condición simple en tu Composable:
@Composable
fun MyLoadingScreen() {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
LinearProgressIndicator(progress = { animatedProgress },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp))
}
}Se usa para mostrar el progreso de una tarea con un porcentaje conocido (de 0.0 a 1.0).
CircularProgressIndicator y LinearProgressIndicator
Un ejemplo en el cual, puedes ver el funcionamiento de ambos composable para el progreso:
@Composable
fun MyDownloadProgress() {
var progress by remember { mutableFloatStateOf(0.5f) }
val animatedProgress by animateFloatAsState(
targetValue = progress,
animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec
)
Column(horizontalAlignment = Alignment.CenterHorizontally) {
// Indicador lineal
LinearProgressIndicator(progress = { animatedProgress },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp))
Spacer(Modifier.height(16.dp))
// Indicador circular
CircularProgressIndicator(progress = { animatedProgress }) // Lambda
// CircularProgressIndicator(progress = animatedProgress ) // deprecated
}
}En este ejemplo, actualizarías la variable progress a medida que tu tarea avanza.
Personalización y Detalles Visuales
Por defecto, estos componentes trabajan con valores entre 0 y 1. Por ejemplo, un valor de 0.5 llenará la barra exactamente hasta la mitad.
Si te posicionas sobre el componente y haces Control + Click, verás todas las propiedades que puedes variar:
- Color: El color de la barra activa.
- Track Color: El color del fondo de la barra (el carril).
- Stroke Width: El grosor del trazo.
Un detalle interesante es que, dependiendo de si usas la versión con lambda o la versión estándar, el acabado visual puede variar ligeramente (como el redondeado de las puntas o el color del carril gris).
Forma Legacy (Sistema de Vistas XML)
Esta es la forma tradicional de agregar indicadores de progreso en Android, utilizando archivos de diseño XML.
Formas de uso de la ProgressBar
Las ProgressBar pueden ser empleadas para indicar el progreso de una tarea en porcentajes (0% a 100%) o de manera indeterminada como la mostrada en la anterior imagen.
Simplemente hay que indicar que se encuentre visible en tiempos de carga y ocultarla (invisible) cuando ya esté el contenido listo; como podemos darnos cuenta, este elemento de interfaz de usuario tiene el inconveniente que no permite bloquear toda la actividad o fragment que lo ejecute y de esta forma evitar que el usuario interactúe con la interfaz, pero como buena práctica dicho bloqueo debería ser realizado de manera manual ocultando o deshabilitando los elementos de la interfaz que consideremos.
Ahora veamos las opciones que tenemos para emplear la ProgressBar:
1. ProgressBar determinada
Para mostrar el progreso de la ProgressBar en forma de porcentajes podemos emplear el atributo android:progress, también podemos variar los niveles de porcentajes para definir una cota mínima y máxima con los atributos android:min y android:max respectivamente:
<ProgressBar
android:id="@+id/determinateBar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:progress="25"/ >El código anterior muestra cómo crear una ProgressBar con progreso determinado con un valor establecido por defecto de 25% que por supuesto podemos variar de manera programática con el método setProgress(int).
Por supuesto, también existen método programáticos para definir la cuota mínima y máxima:
setMax(int max) setMin(int min)2. ProgressBar indeterminada
Para la ProgressBar simplemente debemos de emplear la misma sin los atributos vistos anteriormente; es decir, sin android:progress, android:min y android:max; quedando el código de la siguiente manera:
<ProgressBar
android:id="@+id/indeterminateBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>Puedes consultar la documentación completa en el siguiente enlace: Android Developer ProgressBar.
Caso práctico
En cualquiera de los dos casos anteriores que explicamos, la idea es que al terminar el procesamiento, obtención o cualquier otro proceso que prescinde de mostrar un elemento de carga, ocultar la ProgressBar; él sigue ejemplo práctico muestra un caso práctico empleando la librería de Retrofit que ya es tema de una entrada posterior; en general, mostramos la ProgressBar cuando estamos obteniendo los datos del servidor y la ocultamos una vez que el servidor nos dé una respuesta:
public class FeeActivity extends AppCompatActivity {
private ActionBarDrawerToggle mDrawerToggle;
private static final String TAG = FeeActivity.class.getSimpleName();
private RecyclerView rvNotificaciones;
private RelativeLayout rlSinFees;
private ProgressBar progressBar;
private ArrayList<Fee> fees;
private int loanId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fee);
loanId = getIntent().getExtras().getInt("loan_id");
progressBar = (ProgressBar) findViewById(R.id.progressbar);
rvNotificaciones = (RecyclerView) findViewById(R.id.rv_fees);
rlSinFees = (RelativeLayout) findViewById(R.id.rl_sin_fees);
init();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
}
public void init() {
fees = new ArrayList<>();
generarGridLayout();
cargarCuota();
}
public void generarGridLayout() {
LinearLayoutManager llm = new LinearLayoutManager(this);
rvNotificaciones.setLayoutManager(llm);
rvNotificaciones.setNestedScrollingEnabled(false);
}
public void initAdapter(CuotaAdapter cuotaAdapter) {
rvNotificaciones.setAdapter(cuotaAdapter);
}
public void cargarCuota() {
RestApiAdapter restApiAdapter = new RestApiAdapter();
Gson gson = restApiAdapter.convierteGsonDesearilizadorCuotas();
EndpointsApi endpointsApi = restApiAdapter.establecerConexionRestApi(gson);
Call<FeeResponse> responseCall = endpointsApi.getCuotasDia(UserPreferences.getUsuarioId(FeeActivity.this));
responseCall.enqueue(new Callback<FeeResponse>() {
@Override
public void onResponse(Call<FeeResponse> call, Response<FeeResponse> response) {
FeeResponse feeResponse = response.body();
if (feeResponse != NULL) {
fees = feeResponse.getFees();
}
if (fees.size() == 0) {
rvNotificaciones.setVisibility(View.GONE);
rlSinFees.setVisibility(View.VISIBLE);
}
initAdapter(new CuotaAdapter(fees, FeeActivity.this));
progressBar.setVisibility(View.INVISIBLE);
}
@Override
public void onFailure(Call<FeeResponse> call, Throwable t) {
Log.e("Error", t.toString());
Toast.makeText(FeeActivity.this, getResources().getString(R.string.error_login_local_titulo), Toast.LENGTH_LONG).show();
progressBar.setVisibility(View.INVISIBLE);
}
});
}
}Puedes consultar la documentación completa en los siguientes enlaces:
Siguiente paso, aprende a usar los MediaPlayer para reproducir audios en Android.