androidx.wear.compose.material3.lazy

Interfaces

ResponsiveTransformationSpec

Version of TransformationSpec that supports variable screen sizes.

ResponsiveTransformingLazyColumnScope

Receiver scope for ResponsiveTransformingLazyColumn.

TransformationSpec

Defines visual transformations on the items of a TransformingLazyColumn.

TransformedContainerPainterScope

Provides additional information to the painter inside TransformationSpec.

Classes

ResponsiveItemType

Represents the semantic type of an item in a ResponsiveTransformingLazyColumn.

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

Top-level functions summary

Unit
@Composable
ResponsiveTransformingLazyColumn(
    modifier: Modifier,
    state: TransformingLazyColumnState,
    contentPadding: PaddingValues,
    reverseLayout: Boolean,
    verticalArrangement: Arrangement.Vertical,
    horizontalAlignment: Alignment.Horizontal,
    flingBehavior: FlingBehavior,
    userScrollEnabled: Boolean,
    rotaryScrollableBehavior: RotaryScrollableBehavior?,
    overscrollEffect: OverscrollEffect?,
    content: ResponsiveTransformingLazyColumnScope.() -> Unit
)

A scrolling list that transforms its content items based on their position in the viewport.

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

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

TransformationSpec

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

TransformationSpec

Computes and remembers the appropriate TransformationSpec for the current screen size, given one or more ResponsiveTransformationSpecs for different screen sizes.

Extension functions summary

inline Unit
<T : Any?> ResponsiveTransformingLazyColumnScope.items(
    items: List<T>,
    noinline key: ((index: Int, item) -> Any)?,
    crossinline contentType: (index: Int, item) -> Any?,
    crossinline itemType: (index: Int, item) -> ResponsiveItemType,
    crossinline itemContent: @Composable TransformingLazyColumnItemScope.(item) -> Unit
)

Adds a list of items.

inline Unit
<T : Any?> ResponsiveTransformingLazyColumnScope.itemsIndexed(
    items: List<T>,
    noinline key: ((index: Int, item) -> Any)?,
    crossinline contentType: (index: Int, item) -> Any?,
    crossinline itemType: (index: Int, item) -> ResponsiveItemType,
    crossinline itemContent: @Composable TransformingLazyColumnItemScope.(index: Int, item) -> Unit
)

Adds a list of items where the content of an item is aware of its index.

Modifier

Convenience modifier to calculate transformed height using TransformationSpec.

Top-level functions

ResponsiveTransformingLazyColumn

@Composable
fun ResponsiveTransformingLazyColumn(
    modifier: Modifier = Modifier,
    state: TransformingLazyColumnState = rememberTransformingLazyColumnState(),
    contentPadding: PaddingValues = PaddingValues(),
    reverseLayout: Boolean = false,
    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy( space = 4.dp, alignment = if (!reverseLayout) Alignment.Top else Alignment.Bottom, ),
    horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally,
    flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
    userScrollEnabled: Boolean = true,
    rotaryScrollableBehavior: RotaryScrollableBehavior? = RotaryScrollableDefaults.behavior(state),
    overscrollEffect: OverscrollEffect? = rememberOverscrollEffect(),
    content: ResponsiveTransformingLazyColumnScope.() -> Unit
): Unit

A scrolling list that transforms its content items based on their position in the viewport. Items in the list are lazy loaded.

This component builds upon TransformingLazyColumn by providing responsive behavior, automatically adjusting top and bottom padding based on the screen height and the ResponsiveItemType of the first and last items in the list.

The padding behavior follows Material Design guidelines for Wear OS, ensuring that content is appropriately spaced from the screen edges and that scrolling interactions feel natural. Specifically, it adjusts padding so the first item is initially well-positioned and the last item can scroll to a comfortable viewing position.

import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.ui.Modifier
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.lazy.ResponsiveItemType
import androidx.wear.compose.material3.lazy.ResponsiveTransformingLazyColumn
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight

val transformationSpec = rememberTransformationSpec()
ResponsiveTransformingLazyColumn {
    item(itemType = ResponsiveItemType.ListHeader) {
        ListHeader(modifier = Modifier.transformedHeight(this, transformationSpec)) {
            Text("Header")
        }
    }
    items(count = 10, itemType = { ResponsiveItemType.Button }) { index ->
        Button(
            onClick = {},
            modifier = Modifier.fillMaxWidth().transformedHeight(this, transformationSpec),
            transformation = SurfaceTransformation(transformationSpec),
        ) {
            Text("Item $index")
        }
    }
}
Parameters
modifier: Modifier = Modifier

The modifier to be applied to the ResponsiveTransformingLazyColumn.

state: TransformingLazyColumnState = rememberTransformingLazyColumnState()

The state object that can be used to control and observe the list's scroll position.

contentPadding: PaddingValues = PaddingValues()

Padding around the content. The final top and bottom padding will be the maximum of the values passed in here and the responsive padding calculated by the component to follow Material Design guidelines. This allows developers to enforce a minimum padding (e.g. for global screen insets) while still benefiting from responsive adjustments. Side padding (start and end) will be respected as passed in. Defaults to 0.dp.

reverseLayout: Boolean = false

reverse the direction of scrolling and layout, when true items will be composed from the bottom to the top

verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy( space = 4.dp, alignment = if (!reverseLayout) Alignment.Top else Alignment.Bottom, )

The vertical arrangement of the items, to be used when there is enough space to show all the items. Note that only Arrangement.Top, Arrangement.Center and Arrangement.Bottom arrangements (including their spacedBy variants, i.e., using spacedBy with Alignment.Top, Alignment.CenterVertically and Alignment.Bottom) are supported, The default is Arrangement.Top when reverseLayout is false and Arrangement.Bottom when reverseLayout is true.

horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally

The horizontal alignment of the items.

flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior()

The fling behavior to be used for the list. This parameter and the rotaryScrollableBehavior (which controls rotary scroll) should produce similar scroll effect visually.

userScrollEnabled: Boolean = true

Whether the user should be able to scroll the list. This also affects scrolling with rotary.

rotaryScrollableBehavior: RotaryScrollableBehavior? = RotaryScrollableDefaults.behavior(state)

Parameter for changing rotary scrollable behavior. This parameter and the flingBehavior (which controls touch scroll) should produce similar scroll effect. Can be null if rotary support is not required or when it should be handled externally with a separate Modifier.rotaryScrollable modifier.

overscrollEffect: OverscrollEffect? = rememberOverscrollEffect()

the OverscrollEffect that will be used to render overscroll for this layout. Note that the OverscrollEffect.node will be applied internally as well - you do not need to use Modifier.overscroll separately.

content: ResponsiveTransformingLazyColumnScope.() -> Unit

The DSL block that describes the content of the list using ResponsiveTransformingLazyColumnScope.

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

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

rememberTransformationSpec

@Composable
fun rememberTransformationSpec(): TransformationSpec

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

It would return special NoOp version of TransformationSpec when ReducedMotion is on.

import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.foundation.lazy.items
import androidx.wear.compose.foundation.lazy.rememberTransformingLazyColumnState
import androidx.wear.compose.material3.AppScaffold
import androidx.wear.compose.material3.ListHeader
import androidx.wear.compose.material3.MaterialTheme
import androidx.wear.compose.material3.ScreenScaffold
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.TitleCard
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight

val state = rememberTransformingLazyColumnState()
val transformationSpec = rememberTransformationSpec()
data class NotificationItem(val title: String, val body: String)

val notifications =
    listOf(
        NotificationItem(
            "☕ Coffee Break?",
            "Step away from the screen and grab a pick-me-up. Step away from the screen and grab a pick-me-up.",
        ),
        NotificationItem("🌟 You're Awesome!", "Just a little reminder in case you forgot 😊"),
        NotificationItem("👀 Did you know?", "Check out [app name]'s latest feature update."),
        NotificationItem("📅 Appointment Time", "Your meeting with [name] is in 15 minutes."),
    )
AppScaffold {
    ScreenScaffold(state) { contentPadding ->
        TransformingLazyColumn(state = state, contentPadding = contentPadding) {
            item {
                ListHeader(
                    transformation = SurfaceTransformation(transformationSpec),
                    modifier =
                        Modifier.transformedHeight(this, transformationSpec).animateItem(),
                ) {
                    Text("Notifications")
                }
            }
            items(notifications) { notification ->
                TitleCard(
                    onClick = {},
                    title = {
                        Text(
                            notification.title,
                            fontWeight = FontWeight.Bold,
                            style = MaterialTheme.typography.labelLarge,
                            maxLines = 1,
                        )
                    },
                    subtitle = { Text(notification.body) },
                    transformation = SurfaceTransformation(transformationSpec),
                    modifier =
                        Modifier.transformedHeight(this, transformationSpec).animateItem(),
                )
            }
        }
    }
}

rememberTransformationSpec

@Composable
fun rememberTransformationSpec(vararg specs: ResponsiveTransformationSpec): TransformationSpec

Computes and remembers the appropriate TransformationSpec for the current screen size, given one or more ResponsiveTransformationSpecs for different screen sizes.

It would return special NoOp version of TransformationSpec when ReducedMotion is on.

Example usage for ResponsiveTransformationSpec, the recommended TransformationSpec for large-screen aware Wear apps:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.wear.compose.foundation.lazy.TransformingLazyColumn
import androidx.wear.compose.material3.Button
import androidx.wear.compose.material3.SurfaceTransformation
import androidx.wear.compose.material3.Text
import androidx.wear.compose.material3.lazy.ResponsiveTransformationSpec
import androidx.wear.compose.material3.lazy.TransformationSpec
import androidx.wear.compose.material3.lazy.TransformationVariableSpec
import androidx.wear.compose.material3.lazy.rememberTransformationSpec
import androidx.wear.compose.material3.lazy.transformedHeight

val transformationSpec =
    rememberTransformationSpec(
        ResponsiveTransformationSpec.smallScreen(
            // Makes the content disappear on the edges.
            contentAlpha = TransformationVariableSpec(0f)
        ),
        ResponsiveTransformationSpec.largeScreen(
            // Makes the content disappear on the edges, but a bit more aggressively.
            contentAlpha =
                TransformationVariableSpec(0f, transformationZoneEnterFraction = 0.2f)
        ),
    )
TransformingLazyColumn(
    contentPadding = PaddingValues(20.dp),
    modifier = Modifier.background(Color.Black),
) {
    items(count = 100) { index ->
        Button(
            onClick = {},
            modifier =
                Modifier.fillMaxWidth().transformedHeight(this@items, transformationSpec),
            transformation = SurfaceTransformation(transformationSpec),
        ) {
            Text("Item $index")
        }
    }
}
Parameters
vararg specs: ResponsiveTransformationSpec

The ResponsiveTransformationSpecs that should be used for different screen sizes.

Extension functions

inline fun <T : Any?> ResponsiveTransformingLazyColumnScope.items(
    items: List<T>,
    noinline key: ((index: Int, item) -> Any)? = null,
    crossinline contentType: (index: Int, item) -> Any? = { _, _ -> null },
    crossinline itemType: (index: Int, item) -> ResponsiveItemType = { _, _ -> ResponsiveItemType.Default },
    crossinline itemContent: @Composable TransformingLazyColumnItemScope.(item) -> Unit
): Unit

Adds a list of items.

Parameters
items: List<T>

the data list

noinline key: ((index: Int, item) -> Any)? = null

a factory of stable and unique keys representing the item. Using the same key for multiple items in the ResponsiveTransformingLazyColumn is not allowed. Type of the key should be saveable via Bundle on Android. If null is passed the position in the list will represent the key. When you specify the key the scroll position will be maintained based on the key, which means if you add/remove items before the current visible item the item with the given key will be kept as the first visible one.

crossinline contentType: (index: Int, item) -> Any? = { _, _ -> null }

a factory of the content types for the item. The item compositions of the same type could be reused more efficiently. Note that null is a valid type and items of such type will be considered compatible.

crossinline itemType: (index: Int, item) -> ResponsiveItemType = { _, _ -> ResponsiveItemType.Default }

a factory of the ResponsiveItemTypes for the item. The types of the first and last items in the list determine the amount of responsive padding applied to the top and bottom of the list respectively. Defaults to ResponsiveItemType.Default.

crossinline itemContent: @Composable TransformingLazyColumnItemScope.(item) -> Unit

the content displayed by a single item

inline fun <T : Any?> ResponsiveTransformingLazyColumnScope.itemsIndexed(
    items: List<T>,
    noinline key: ((index: Int, item) -> Any)? = null,
    crossinline contentType: (index: Int, item) -> Any? = { _, _ -> null },
    crossinline itemType: (index: Int, item) -> ResponsiveItemType = { _, _ -> ResponsiveItemType.Default },
    crossinline itemContent: @Composable TransformingLazyColumnItemScope.(index: Int, item) -> Unit
): Unit

Adds a list of items where the content of an item is aware of its index.

Parameters
items: List<T>

the data list

noinline key: ((index: Int, item) -> Any)? = null

a factory of stable and unique keys representing the item. Using the same key for multiple items in the ResponsiveTransformingLazyColumn is not allowed. Type of the key should be saveable via Bundle on Android. If null is passed the position in the list will represent the key. When you specify the key the scroll position will be maintained based on the key, which means if you add/remove items before the current visible item the item with the given key will be kept as the first visible one.

crossinline contentType: (index: Int, item) -> Any? = { _, _ -> null }

a factory of the content types for the item. The item compositions of the same type could be reused more efficiently. Note that null is a valid type and items of such type will be considered compatible.

crossinline itemType: (index: Int, item) -> ResponsiveItemType = { _, _ -> ResponsiveItemType.Default }

a factory of the ResponsiveItemTypes for the item. The types of the first and last items in the list determine the amount of responsive padding applied to the top and bottom of the list respectively. Defaults to ResponsiveItemType.Default.

crossinline itemContent: @Composable TransformingLazyColumnItemScope.(index: Int, item) -> Unit

the content displayed by a single item

transformedHeight

fun Modifier.transformedHeight(
    scope: TransformingLazyColumnItemScope,
    transformationSpec: TransformationSpec
): Modifier

Convenience modifier to calculate transformed height using TransformationSpec.