placeholderShimmer

Composable Function

Android
@Composable
public fun Modifier.placeholderShimmer(
    placeholderState: PlaceholderState,
    shape: Shape = PlaceholderDefaults.shape,
    color: Color = PlaceholderDefaults.shimmerColor,
): Modifier

Modifier.placeholderShimmer draws a periodic shimmer over content, indicating to the user that contents are loading or potentially out of date. The placeholder shimmer is a 45 degree gradient from Top|Left of the screen to Bottom|Right. The shimmer is coordinated via the animation frame clock which orchestrates the shimmer so that every component will shimmer as the gradient progresses across the screen. NOTE: For animations to work, an AppScaffold should be used.

Example of a Button with icon and a label that put placeholders over individual content slots and then draws a placeholder shimmer over the result:

Example of a simple text placeholder:

Parameters

placeholderStatethe current placeholder state that determine whether the placeholder shimmer should be shown.
shapethe shape of the component.
colorthe color to use in the shimmer.

Code Examples

ButtonWithIconAndLabelAndPlaceholders

/**
 * This sample applies placeholders directly over the content that is waiting to be loaded. This
 * approach is suitable for situations where the developer has an approximate knowledge of how big
 * the content is going to be and it doesn't have cached data that can be shown.
 */
@Composable
fun ButtonWithIconAndLabelAndPlaceholders() {
    var labelText by remember { mutableStateOf("") }
    var imageVector: ImageVector? by remember { mutableStateOf(null) }
    val buttonPlaceholderState =
        rememberPlaceholderState(isVisible = labelText.isEmpty() || imageVector == null)
    FilledTonalButton(
        onClick = { /* Do something */ },
        enabled = true,
        modifier = Modifier.fillMaxWidth().placeholderShimmer(buttonPlaceholderState),
        label = {
            Text(
                text = labelText,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.fillMaxWidth().placeholder(buttonPlaceholderState),
            )
        },
        icon = {
            Box(
                modifier =
                    Modifier.size(ButtonDefaults.IconSize).placeholder(buttonPlaceholderState)
            ) {
                if (imageVector != null) {
                    Icon(
                        imageVector = imageVector!!,
                        contentDescription = "Heart",
                        modifier =
                            Modifier.wrapContentSize(align = Alignment.Center)
                                .size(ButtonDefaults.IconSize)
                                .fillMaxSize(),
                    )
                }
            }
        },
    )
    // Simulate content loading completing in stages
    LaunchedEffect(Unit) {
        delay(2000)
        imageVector = Icons.Filled.Favorite
        delay(1000)
        labelText = "A label"
    }
}

TextPlaceholder

/**
 * This sample applies a placeholder and placeholderShimmer directly over a single composable.
 *
 * Note that the modifier ordering is important, the placeholderShimmer must be before the
 * placeholder in the modifier chain - otherwise the shimmer will be drawn underneath the
 * placeholder and will not be visible.
 */
@Composable
fun TextPlaceholder() {
    var labelText by remember { mutableStateOf("") }
    val placeholderState = rememberPlaceholderState(isVisible = labelText.isEmpty())
    Text(
        text = labelText,
        overflow = TextOverflow.Ellipsis,
        textAlign = TextAlign.Center,
        modifier =
            Modifier.width(90.dp).placeholderShimmer(placeholderState).placeholder(placeholderState),
    )
    // Simulate content loading
    LaunchedEffect(Unit) {
        delay(3000)
        labelText = "A label"
    }
}