placeholder
Android
Modifier in Wear Material Compose
Draws a placeholder shape over the top of a composable and animates a wipe off effect to remove the placeholder. Typically used whilst content is 'loading' and then 'revealed'.
Last updated:
Installation
dependencies {
implementation("androidx.wear.compose:compose-material:1.5.0-alpha01")
}
Overloads
@ExperimentalWearMaterialApi
@Composable
fun Modifier.placeholder(
placeholderState: PlaceholderState,
shape: Shape = MaterialTheme.shapes.small,
color: Color =
MaterialTheme.colors.onSurface
.copy(alpha = 0.1f)
.compositeOver(MaterialTheme.colors.surface)
): Modifier
Parameters
name | description |
---|---|
placeholderState | determines whether the placeholder is visible and controls animation effects for the placeholder. |
shape | the shape to apply to the placeholder |
color | the color of the placeholder. |
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() }
}
}