Interface

TransformationSpec

Defines visual transformations on the items of a [TransformingLazyColumn].

CustomCompositingStrategyTransformationSpecSample

@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) }
                        },
                )
            }
        }
    }
}

CustomTransformationSpecSample

@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)
                            }
                        },
                )
            }
        }
    }
}

TransformationSpecButtonRowSample

@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)
                                    }
                                },
                        )
                    }
                }
            }
        }
    }
}