We just launched Compose Examples featuring over 150+ components! Check it out →

ContainedLoadingIndicator

Common

Component in Material 3 Compose

A Material Design contained loading indicator.

This version of the loading indicator morphs between its [polygons] shapes by the value of its [progress]. The shapes in this variation are contained within a colored [containerShape].

Contained loading indicator
image

Last updated:

Installation

dependencies {
   implementation("androidx.compose.material3:material3:1.4.0-alpha01")
}

Overloads

@ExperimentalMaterial3ExpressiveApi
@Composable
fun ContainedLoadingIndicator(
    progress: () -> Float,
    modifier: Modifier = Modifier,
    containerColor: Color = LoadingIndicatorDefaults.containedContainerColor,
    indicatorColor: Color = LoadingIndicatorDefaults.containedIndicatorColor,
    containerShape: Shape = LoadingIndicatorDefaults.containerShape,
    polygons: List<RoundedPolygon> = LoadingIndicatorDefaults.DeterminateIndicatorPolygons
)

Parameters

namedescription
progressthe progress of this loading indicator, where 0.0 represents no progress and 1.0 represents full progress. Values outside of this range are coerced into the range. The indicator will morph its shapes between the provided [polygons] according to the value of the progress.
modifierthe [Modifier] to be applied to this loading indicator
containerColorthe loading indicator's container color
indicatorColorthe loading indicator's color
containerShapethe loading indicator's container shape
polygonsa list of [RoundedPolygon]s for the sequence of shapes this loading indicator will morph between as it progresses from 0.0 to 1.0. The loading indicator expects at least two items in that list.
@ExperimentalMaterial3ExpressiveApi
@Composable
fun ContainedLoadingIndicator(
    modifier: Modifier = Modifier,
    containerColor: Color = LoadingIndicatorDefaults.containedContainerColor,
    indicatorColor: Color = LoadingIndicatorDefaults.containedIndicatorColor,
    containerShape: Shape = LoadingIndicatorDefaults.containerShape,
    polygons: List<RoundedPolygon> = LoadingIndicatorDefaults.IndeterminateIndicatorPolygons,
)

Parameters

namedescription
modifierthe [Modifier] to be applied to this loading indicator
containerColorthe loading indicator's container color
indicatorColorthe loading indicator's color
containerShapethe loading indicator's container shape
polygonsa list of [RoundedPolygon]s for the sequence of shapes this loading indicator will morph between. The loading indicator expects at least two items in that list.

Code Examples

DeterminateContainedLoadingIndicatorSample

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun DeterminateContainedLoadingIndicatorSample() {
    var progress by remember { mutableFloatStateOf(0f) }
    val animatedProgress by
        animateFloatAsState(
            targetValue = progress,
            animationSpec =
                spring(
                    dampingRatio = Spring.DampingRatioNoBouncy,
                    stiffness = Spring.StiffnessVeryLow,
                    visibilityThreshold = 1 / 1000f
                )
        )

    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        ContainedLoadingIndicator(progress = { animatedProgress })
        Spacer(Modifier.requiredHeight(30.dp))
        Text("Set loading progress:")
        Slider(
            modifier = Modifier.width(300.dp),
            value = progress,
            valueRange = 0f..1f,
            onValueChange = { progress = it },
        )
    }
}

LoadingIndicatorPullToRefreshSample

@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@Composable
@Preview
fun LoadingIndicatorPullToRefreshSample() {
    var itemCount by remember { mutableStateOf(15) }
    var isRefreshing by remember { mutableStateOf(false) }
    val state = rememberPullToRefreshState()
    val coroutineScope = rememberCoroutineScope()
    val onRefresh: () -> Unit = {
        isRefreshing = true
        coroutineScope.launch {
            // fetch something
            delay(5000)
            itemCount += 5
            isRefreshing = false
        }
    }

    val scaleFraction = {
        if (isRefreshing) 1f
        else LinearOutSlowInEasing.transform(state.distanceFraction).coerceIn(0f, 1f)
    }

    Scaffold(
        modifier =
            Modifier.pullToRefresh(
                state = state,
                isRefreshing = isRefreshing,
                onRefresh = onRefresh
            ),
        topBar = {
            TopAppBar(
                title = { Text("TopAppBar") },
                // Provide an accessible alternative to trigger refresh.
                actions = {
                    IconButton(onClick = onRefresh) {
                        Icon(Icons.Filled.Refresh, "Trigger Refresh")
                    }
                }
            )
        }
    ) {
        Box(Modifier.padding(it)) {
            LazyColumn(Modifier.fillMaxSize()) {
                if (!isRefreshing) {
                    items(itemCount) { ListItem({ Text(text = "Item ${itemCount - it}") }) }
                }
            }
            Box(
                Modifier.align(Alignment.TopCenter).graphicsLayer {
                    scaleX = scaleFraction()
                    scaleY = scaleFraction()
                }
            ) {
                PullToRefreshDefaults.LoadingIndicator(state = state, isRefreshing = isRefreshing)
            }
        }
    }
}

ContainedLoadingIndicatorSample

@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Preview
@Composable
fun ContainedLoadingIndicatorSample() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) { ContainedLoadingIndicator() }
}
by @alexstyl