placeholderShimmer
@ExperimentalWearMaterialApi
@Composable
public fun Modifier.placeholderShimmer(
placeholderState: PlaceholderState,
shape: Shape = MaterialTheme.shapes.small,
color: Color = MaterialTheme.colors.onSurface,
): Modifier
Modifier to draw a placeholder shimmer over a component. 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.
Example of a Chip
with icon and a label that put placeholders over individual content slots and
then draws a placeholder shimmer over the result:
Example of a Chip
with icon and a primary and secondary labels that draws another Chip
over
the top of it when waiting for placeholder data to load and then draws a placeholder shimmer over
the top:
NOTE: The order of modifiers is important. If you are adding both Modifier.placeholder
and
Modifier.placeholderShimmer
to the same composable then the shimmer must be before in the
modifier chain. Example of Text
composable with both placeholderShimmer and placeholder
modifiers.
Parameters
placeholderState | the current placeholder state that determine whether the placeholder shimmer should be shown. |
shape | the shape of the component. |
color | the color to use in the shimmer. |
Code Examples
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() }
}
}