androidx.wear.compose.material3.lazy

Interfaces

ResponsiveTransformationSpec

Version of TransformationSpec that supports variable screen sizes.

TransformationSpec

Defines visual transformations on the items of a TransformingLazyColumn.

TransformedContainerPainterScope

Provides additional information to the painter inside TransformationSpec.

Classes

TransformationVariableSpec

This class represents the configuration parameters for one variable that changes as the item moves on the screen and will be used to apply the corresponding transformation - for example: container alpha.

Objects

Composables

rememberTransformationSpec

Computes and remembers the appropriate TransformationSpec for the current screen size.

rememberTransformingLazyColumnFirstLayoutItemProvider

Creates and remembers a TransformingLazyColumnFirstLayoutItemProvider that delegates to the provided itemInfo lambda.

Modifiers

transformedHeight

Convenience modifier to calculate transformed height using TransformationSpec.

Top-level functions summary

TransformationVariableSpec
lerp(
    start: TransformationVariableSpec,
    stop: TransformationVariableSpec,
    progress: Float
)

Helper function to lerp between the variables for different screen sizes.

Extension functions summary

Extension properties summary

Top-level functions

fun lerp(
    start: TransformationVariableSpec,
    stop: TransformationVariableSpec,
    progress: Float
): TransformationVariableSpec

Helper function to lerp between the variables for different screen sizes.

Extension functions

TransformingLazyColumnState.layoutItemInfoOf

fun TransformingLazyColumnState.layoutItemInfoOf(
    itemKey: Any,
    itemEdge: TransformingLazyColumnFirstLayoutItemProvider.ItemEdge
): TransformingLazyColumnFirstLayoutItemProvider.ItemInfo?

Returns the TransformingLazyColumnFirstLayoutItemProvider.ItemInfo for the visible item with the given itemKey, aligned to the specified itemEdge. Returns null if the item is not currently visible on screen.

This helper is useful when building a custom TransformingLazyColumnFirstLayoutItemProvider that needs to track and stabilize the position of a specific key (e.g. an expanding item) during content size animations.

import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.TransformingLazyColumnFirstLayoutItemProvider
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Card
import androidx.wear.compose.material3.CardDefaults
import androidx.wear.compose.material3.OutlinedCard
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.lazy.layoutItemInfoOf
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.rememberTransformingLazyColumnFirstLayoutItemProvider
import androidx.wear.compose.material3.lazy.transformedHeight

val state = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()

// This sample demonstrates how to use rememberTransformingLazyColumnFirstLayoutItemProvider
// to control the expansion direction of a dynamically resizing item. By using the voice input
// card's Bottom/End edge as the layout reference, the card predictably expands upwards.
// The constant key safely falls back to default layout behavior when scrolled off-screen.
val firstLayoutItemProvider = rememberTransformingLazyColumnFirstLayoutItemProvider {
    state.layoutItemInfoOf(
        itemKey = "voice_input",
        itemEdge = TransformingLazyColumnFirstLayoutItemProvider.ItemEdge.End,
    )
}

var isListening by remember { mutableStateOf(false) }
var lineCount by remember { mutableIntStateOf(0) }
// Simple loop simulating text lines arriving over time
LaunchedEffect(isListening) {
    if (isListening) {
        while (lineCount < 5) {
            delay(300)
            lineCount++
        }
        isListening = false
    }
}

AppScaffold {
    ScreenScaffold(state) { contentPadding ->
        TransformingLazyColumn(
            state = state,
            contentPadding = contentPadding,
            firstLayoutItemProvider = firstLayoutItemProvider,
        ) {
            items(count = 3, key = { "message_$it" }) { index ->
                Card(
                    onClick = {},
                    modifier =
                        Modifier.minimumVerticalContentPadding(
                                CardDefaults.minimumVerticalListContentPadding
                            )
                            .transformedHeight(this, transformationSpec)
                            .animateItem(placementSpec = null),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text("Previous message $index")
                }
            }

            item(key = "voice_input") {
                val voiceText = (1..lineCount).joinToString("\n") { "Voice Input line $it" }

                OutlinedCard(
                    onClick = {
                        if (lineCount >= 5) lineCount = 0
                        isListening = !isListening
                    },
                    modifier =
                        Modifier.minimumVerticalContentPadding(
                                CardDefaults.minimumVerticalListContentPadding
                            )
                            .transformedHeight(this, transformationSpec)
                            .animateItem(placementSpec = null),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text(text = voiceText.ifEmpty { "Tap to speak" })
                }
            }
        }
    }
}
Parameters
itemKey: Any

The unique key of the item to search for.

itemEdge: TransformingLazyColumnFirstLayoutItemProvider.ItemEdge

The TransformingLazyColumnFirstLayoutItemProvider.ItemEdge (Start or End) of the item to use for the layout reference.

Extension properties

TransformingLazyColumnState.firstVisibleItemLayoutItemInfo

val TransformingLazyColumnState.firstVisibleItemLayoutItemInfoTransformingLazyColumnFirstLayoutItemProvider.ItemInfo?

Returns the TransformingLazyColumnFirstLayoutItemProvider.ItemInfo for the first visible item aligned to its TransformingLazyColumnFirstLayoutItemProvider.ItemEdge.Start edge, or null if there are no visible items.

This extension is useful when building a custom TransformingLazyColumnFirstLayoutItemProvider that needs to ensure the first visible item maintains a static start edge during dynamic content updates (such as item additions, removals, or size changes), avoiding visual jumps in the viewport.

Since the first visible item changes rapidly during an active scroll, tracking it continuously does not provide a stable visual reference. When using this property with rememberTransformingLazyColumnFirstLayoutItemProvider, it is highly recommended to yield to the default layout behavior by returning null when TransformingLazyColumnState.isScrollInProgress is true. This allows the list to fall back to its default layout behavior, which accurately tracks the scroll gesture, while also avoiding unnecessary computational overhead.

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.TransformingLazyColumnFirstLayoutItemProvider
import androidx.wear.compose.foundation.lazy.itemsIndexed
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.Card
import androidx.wear.compose.material3.CardDefaults
import androidx.wear.compose.material3.CompactButton
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.lazy.firstVisibleItemLayoutItemInfo
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.rememberTransformingLazyColumnFirstLayoutItemProvider
import androidx.wear.compose.material3.lazy.transformedHeight

val transformationSpec = rememberTransformationSpec()
val state = rememberTransformingLazyColumnState()
var elements by remember { mutableStateOf(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) }
var nextElement by remember { mutableIntStateOf(10) }

fun addCardAfter(index: Int) {
    elements =
        elements.subList(0, index + 1) +
            listOf(nextElement++) +
            elements.subList(index + 1, elements.count())
}

fun removeCardAt(index: Int) {
    elements = elements.subList(0, index) + elements.subList(index + 1, elements.count())
}

// This sample demonstrates how to use rememberTransformingLazyColumnFirstVisibleItemProvider
// to maintain a stable viewport top when items are added or removed dynamically.
// It achieves this by using the first visible item as the layout reference.
AppScaffold {
    ScreenScaffold(state) { contentPadding ->
        TransformingLazyColumn(
            state = state,
            contentPadding = contentPadding,
            firstLayoutItemProvider =
                rememberTransformingLazyColumnFirstLayoutItemProvider {
                    if (state.isScrollInProgress) null else state.firstVisibleItemLayoutItemInfo
                },
        ) {
            itemsIndexed(elements, key = { _, key -> key }) { index, cardKey ->
                Card(
                    onClick = {},
                    modifier =
                        Modifier.minimumVerticalContentPadding(
                                CardDefaults.minimumVerticalListContentPadding
                            )
                            .transformedHeight(this, transformationSpec)
                            .animateItem(),
                    transformation = SurfaceTransformation(transformationSpec),
                ) {
                    Text("Card $cardKey")
                    Row {
                        Spacer(modifier = Modifier.weight(1f))
                        CompactButton(
                            onClick = { removeCardAt(index) },
                            enabled = elements.count() > 1,
                        ) {
                            Text("-")
                        }
                        CompactButton(onClick = { addCardAfter(index) }) { Text("+") }
                    }
                }
            }
        }
    }
}