TransformationSpec
public interface TransformationSpec
Defines visual transformations on the items of a TransformingLazyColumn.
When using this API, users need to make similar changes between all of the functions. For
example, if getTransformedHeight returns half of the size of the item, then transformation
functions should do the same, scaling or cropping the item.
getTransformedHeight is called first, then the painter would be created and then container and
content transformations are applied.
This shows how to create a custom transformation spec for the TransformingLazyColumn.
This shows how to apply different androidx.compose.ui.graphics.CompositingStrategy with
TransformationSpec for the TransformingLazyColumn.
This shows how to apply the TransformationSpec to custom component inside
TransformingLazyColumn.
Functions
public fun getTransformedHeight(
measuredHeight: Int,
scrollProgress: TransformingLazyColumnItemScrollProgress,
): Int
Calculates the transformed height to be passed into
TransformingLazyColumnItemScope.transformedHeight based on the parameters for the spec.
Parameters
| measuredHeight | The height in pixels of the item returned during measurement. |
| scrollProgress | The scroll progress of the item. |
public fun GraphicsLayerScope.applyContentTransformation(
scrollProgress: TransformingLazyColumnItemScrollProgress
)
Visual transformations to be applied to the content of the item as it scrolls.
Parameters
| scrollProgress | The scroll progress of the item. |
public fun GraphicsLayerScope.applyContainerTransformation(
scrollProgress: TransformingLazyColumnItemScrollProgress
)
Visual transformations to be applied to the container of the item as it scrolls.
Parameters
| scrollProgress | The scroll progress of the item. |
public fun TransformedContainerPainterScope.createTransformedContainerPainter(
painter: Painter,
shape: Shape,
border: BorderStroke?,
): Painter
Returns a new painter to be used instead of painter which should react on a transformation.
Parameters
| painter | The painter to be transformed. This is the original Painter the component was trying to use. |
| shape | The shape of the item's container. |
| border | The border of the item's container. |
Code Examples
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)
}
},
)
}
}
}
}
}
}