Compose Modifier

placeholderShimmer

Modifier to draw a placeholder shimmer over a component.

ChipWithIconAndLabelAndPlaceholders

/**
 * This sample applies placeholders directly over the content that is waiting to be loaded. This
 * approach is suitable for situations where the developer is confident that the stadium shaped
 * placeholder will cover the content in the period between when it is loaded and when the
 * placeholder visual effects will have finished.
 */
@OptIn(ExperimentalWearMaterialApi::class)
@Composable
fun ChipWithIconAndLabelAndPlaceholders() {
    var labelText by remember { mutableStateOf("") }
    var iconResource: Int? by remember { mutableStateOf(null) }
    val chipPlaceholderState = rememberPlaceholderState {
        labelText.isNotEmpty() && iconResource != null
    }
    Chip(
        onClick = { /* Do something */ },
        enabled = true,
        label = {
            Text(
                text = labelText,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.fillMaxWidth().placeholder(chipPlaceholderState),
            )
        },
        icon = {
            Box(modifier = Modifier.size(ChipDefaults.IconSize).placeholder(chipPlaceholderState)) {
                if (iconResource != null) {
                    Icon(
                        painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
                        contentDescription = "airplane",
                        modifier =
                            Modifier.wrapContentSize(align = Alignment.Center)
                                .size(ChipDefaults.IconSize)
                                .fillMaxSize(),
                    )
                }
            }
        },
        colors =
            PlaceholderDefaults.placeholderChipColors(
                originalChipColors = ChipDefaults.primaryChipColors(),
                placeholderState = chipPlaceholderState,
            ),
        modifier = Modifier.fillMaxWidth().placeholderShimmer(chipPlaceholderState),
    )
    // Simulate content loading completing in stages
    LaunchedEffect(Unit) {
        delay(2000)
        iconResource = R.drawable.ic_airplanemode_active_24px
        delay(1000)
        labelText = "A label"
    }
    if (!chipPlaceholderState.isShowContent) {
        LaunchedEffect(chipPlaceholderState) { chipPlaceholderState.startPlaceholderAnimation() }
    }
}

ChipWithIconAndLabelsAndOverlaidPlaceholder

/**
 * This sample places a [Chip] with placeholder effects applied to it on top of the [Chip] that
 * contains the actual content.
 *
 * This approach is needed in situations where the developer wants a higher degree of control over
 * what is shown as a placeholder before the loaded content is revealed. This approach can be used
 * when there would otherwise be content becoming visible as it is loaded and before the
 * placeholders have been wiped-off to reveal the content underneath, e.g. If the content contains
 * left|top aligned Text that would be visible in the part of the content slot not covered by the
 * stadium placeholder shape.
 */
@OptIn(ExperimentalWearMaterialApi::class)
@Composable
fun ChipWithIconAndLabelsAndOverlaidPlaceholder() {
    var labelText by remember { mutableStateOf("") }
    var secondaryLabelText by remember { mutableStateOf("") }
    var iconResource: Int? by remember { mutableStateOf(null) }
    val chipPlaceholderState = rememberPlaceholderState {
        labelText.isNotEmpty() && secondaryLabelText.isNotEmpty() && iconResource != null
    }
    Box {
        if (chipPlaceholderState.isShowContent || chipPlaceholderState.isWipeOff) {
            Chip(
                onClick = { /* Do something */ },
                enabled = true,
                label = {
                    Text(
                        text = labelText,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.fillMaxWidth(),
                    )
                },
                secondaryLabel = {
                    Text(
                        text = secondaryLabelText,
                        maxLines = 1,
                        overflow = TextOverflow.Ellipsis,
                        modifier = Modifier.fillMaxWidth(),
                    )
                },
                icon = {
                    if (iconResource != null) {
                        Icon(
                            painter = painterResource(id = R.drawable.ic_airplanemode_active_24px),
                            contentDescription = "airplane",
                            modifier =
                                Modifier.wrapContentSize(align = Alignment.Center)
                                    .size(ChipDefaults.IconSize),
                        )
                    }
                },
                colors = ChipDefaults.gradientBackgroundChipColors(),
                modifier = Modifier.fillMaxWidth(),
            )
        }
        if (!chipPlaceholderState.isShowContent) {
            Chip(
                onClick = { /* Do something */ },
                enabled = true,
                label = {
                    Box(
                        modifier =
                            Modifier.fillMaxWidth()
                                .height(16.dp)
                                .padding(top = 1.dp, bottom = 1.dp)
                                .placeholder(placeholderState = chipPlaceholderState)
                    )
                },
                secondaryLabel = {
                    Box(
                        modifier =
                            Modifier.fillMaxWidth()
                                .height(16.dp)
                                .padding(top = 1.dp, bottom = 1.dp)
                                .placeholder(placeholderState = chipPlaceholderState)
                    )
                },
                icon = {
                    Box(
                        modifier =
                            Modifier.size(ChipDefaults.IconSize).placeholder(chipPlaceholderState)
                    )
                    // Simulate the icon becoming ready after a period of time
                    LaunchedEffect(Unit) {
                        delay(2000)
                        iconResource = R.drawable.ic_airplanemode_active_24px
                    }
                },
                colors =
                    PlaceholderDefaults.placeholderChipColors(
                        placeholderState = chipPlaceholderState
                    ),
                modifier = Modifier.fillMaxWidth().placeholderShimmer(chipPlaceholderState),
            )
        }
    }
    // Simulate data being loaded after a delay
    LaunchedEffect(Unit) {
        delay(2500)
        secondaryLabelText = "A secondary label"
        delay(500)
        labelText = "A label"
    }
    if (!chipPlaceholderState.isShowContent) {
        LaunchedEffect(chipPlaceholderState) { chipPlaceholderState.startPlaceholderAnimation() }
    }
}

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.
 */
@OptIn(ExperimentalWearMaterialApi::class)
@Composable
fun TextPlaceholder() {
    var labelText by remember { mutableStateOf("") }
    val chipPlaceholderState = rememberPlaceholderState { labelText.isNotEmpty() }
    Text(
        text = labelText,
        overflow = TextOverflow.Ellipsis,
        textAlign = TextAlign.Center,
        modifier =
            Modifier.width(90.dp)
                .placeholderShimmer(chipPlaceholderState)
                .placeholder(chipPlaceholderState),
    )
    // Simulate content loading
    LaunchedEffect(Unit) {
        delay(3000)
        labelText = "A label"
    }
    if (!chipPlaceholderState.isShowContent) {
        LaunchedEffect(chipPlaceholderState) { chipPlaceholderState.startPlaceholderAnimation() }
    }
}