Defines visual transformations on the items of a [TransformingLazyColumn].
@Composable
@Preview
fun CustomCompositingStrategyTransformationSpecSample() {
val transformationSpec = rememberTransformationSpec()
TransformingLazyColumn(
contentPadding = PaddingValues(20.dp),
modifier = Modifier.background(Color.Black),
) {
items(count = 100) { index ->
Button(
onClick = {},
modifier =
Modifier.fillMaxWidth()
.transformedHeight(this, transformationSpec)
.graphicsLayer {
with(transformationSpec) {
applyContainerTransformation(scrollProgress)
}
// Using CompositingStrategy.ModulateAlpha can provide better
// performance when a container transformation involves alpha rendering,
// as it avoids an extra offscreen buffer.
//
// However, care must be taken with overlapping or transparent content
// inside the container. If the content itself uses alpha, ModulateAlpha
// can lead to multiple, incorrect alpha blending with the background
// (double-blending artifacts).
//
// To prevent this, the content's drawing layer must explicitly use
// CompositingStrategy.Offscreen to ensure internal elements are
// correctly pre-blended before the outer ModulateAlpha is applied.
compositingStrategy = ModulateAlpha
},
) {
Text(
text = "Item $index",
modifier =
Modifier.graphicsLayer {
// Ensure content layer uses CompositingStrategy.Offscreen when
// container uses CompositingStrategy.ModulateAlpha.
// This composition is required to guarantee correct visual blending of
// content that contains internal alpha or complex blending.
// compositingStrategy is set to CompositingStrategy.Offscreen inside
// applyContentTransformation.
with(transformationSpec) { applyContentTransformation(scrollProgress) }
},
)
}
}
}
}
@Composable
@Preview
fun CustomTransformationSpecSample() {
val transformationSpec = rememberTransformationSpec()
val morphingTransformationSpec =
object : TransformationSpec by transformationSpec {
override fun GraphicsLayerScope.applyContainerTransformation(
scrollProgress: TransformingLazyColumnItemScrollProgress
) {
with(transformationSpec) { applyContainerTransformation(scrollProgress) }
rotationX = (scrollProgress.topOffsetFraction - 0.5f).coerceIn(0f..1f) * 270f
}
}
TransformingLazyColumn(
contentPadding = PaddingValues(20.dp),
modifier = Modifier.background(Color.Black),
) {
items(count = 100) { index ->
Button(
onClick = {},
modifier =
Modifier.fillMaxWidth()
.transformedHeight(this, morphingTransformationSpec)
.graphicsLayer {
with(morphingTransformationSpec) {
applyContainerTransformation(scrollProgress)
}
},
) {
Text(
"Item $index",
modifier =
Modifier.graphicsLayer {
with(morphingTransformationSpec) {
applyContentTransformation(scrollProgress)
}
},
)
}
}
}
}
@Composable
@Preview
fun TransformationSpecButtonRowSample() {
// Use the spec derived from default small and large screen specs.
val transformationSpec = rememberTransformationSpec()
TransformingLazyColumn(
contentPadding = PaddingValues(20.dp),
modifier = Modifier.background(Color.Black),
) {
items(count = 100) {
val interactionSource1 = remember { MutableInteractionSource() }
val interactionSource2 = remember { MutableInteractionSource() }
ButtonGroup(
modifier =
Modifier.fillMaxWidth()
.graphicsLayer {
with(transformationSpec) {
applyContainerTransformation(scrollProgress)
}
}
.transformedHeight(this, transformationSpec)
) {
Button(
onClick = {},
modifier = Modifier.animateWidth(interactionSource1),
interactionSource = interactionSource1,
) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
"L",
modifier =
Modifier.graphicsLayer {
with(transformationSpec) {
applyContentTransformation(scrollProgress)
}
},
)
}
}
Button(
onClick = {},
modifier = Modifier.animateWidth(interactionSource2),
interactionSource = interactionSource2,
) {
Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
"R",
modifier =
Modifier.graphicsLayer {
with(transformationSpec) {
applyContentTransformation(scrollProgress)
}
},
)
}
}
}
}
}
}