Provides the first item to layout for TransformingLazyColumn.
@Preview
@Composable
fun TransformingLazyColumnFirstLayoutItemProviderSample() {
val state = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
var expandedItemIndex by remember { mutableIntStateOf(-1) }
// This sample demonstrates how to use the provider API to control the direction of content
// shifting. By default, TransformingLazyColumn uses the center item as the layout reference.
// This means that if an item above the center expands, it pushes content upwards;
// if below, it pushes downwards.
//
// Here, we fix the Bottom/End edge of the clicked item regardless of its position on screen,
// so that when its animated content appears, the card predictably expands *upwards* every time.
val upwardExpandingItemProvider =
remember(state) {
TransformingLazyColumnFirstLayoutItemProvider { centerItem ->
val item = expandedItemIndex
// Yield to the standard layout behavior during active scrolls.
// This avoids custom layout overhead and ensures the [TransformingLazyColumn]
// tracks the user's scroll gesture using its default center layout reference.
if (item == -1 || state.isScrollInProgress) {
return@TransformingLazyColumnFirstLayoutItemProvider centerItem
}
// Look up the item's offset from state.layoutInfo (which holds the details
// from the previous measure pass) to maintain its visual position in the current
// pass.
state.layoutInfo.visibleItems
.fastFirstOrNull { visibleItem -> visibleItem.index == item }
?.let { visibleItem ->
TransformingLazyColumnFirstLayoutItemProvider.ItemInfo(
key = visibleItem.key,
index = visibleItem.index,
// Pin the bottom edge of the item
itemEdge = ItemEdge.End,
// Calculate the exact bottom offset from the previous pass
offset = visibleItem.offset + visibleItem.transformedHeight,
)
} ?: centerItem
}
}
TransformingLazyColumn(
state = state,
contentPadding = PaddingValues(horizontal = 20.dp),
firstLayoutItemProvider = upwardExpandingItemProvider,
) {
items(count = 10, key = { it }) { cardIndex ->
val isExpanded = expandedItemIndex == cardIndex
TitleCard(
onClick = { expandedItemIndex = cardIndex },
modifier =
Modifier.minimumVerticalContentPadding(
CardDefaults.minimumVerticalListContentPadding
)
.fillMaxWidth()
.transformedHeight(this, transformationSpec),
transformation = SurfaceTransformation(transformationSpec),
title = { Text("Card $cardIndex") },
subtitle = {
AnimatedVisibility(isExpanded) { Text("Expanded content is available here") }
},
content = { Text("Tap to expand") },
)
}
}
}