scrollTransform
Android
Modifier in Wear Material 3 Compose
A modifier that enables Material3 Motion transformations for content within a [TransformingLazyColumn] item. It also draws the background behind the content using Material3 Motion transformations.
This modifier calculates and applies transformations to the content based on the [TransformingLazyColumnItemScrollProgress] of the item inside the [TransformingLazyColumn]. It adjusts the height, position, applies scaling and morphing effects as the item scrolls.
When [ReduceMotion] is enabled, this modifier will not apply any transformations.
Last updated:
Installation
dependencies {
implementation("androidx.wear.compose:compose-material3:1.0.0-alpha32")
}
Overloads
@Composable
fun Modifier.scrollTransform(
scope: TransformingLazyColumnItemScope,
backgroundColor: Color,
shape: Shape = RectangleShape
): Modifier
Parameters
name | description |
---|---|
scope | The [TransformingLazyColumnItemScope] provides access to the item's index and key. |
backgroundColor | Color of the background. |
shape | Shape of the background. |
@Composable
fun Modifier.scrollTransform(
scope: TransformingLazyColumnItemScope,
shape: Shape,
painter: Painter,
border: BorderStroke? = null
): Modifier
Parameters
name | description |
---|---|
scope | The [TransformingLazyColumnItemScope] provides access to the item's index and key. |
shape | [Shape] of the background. |
painter | [Painter] to use for the background. |
border | Border to draw around the background, or null if no border is needed. |
@Composable
fun Modifier.scrollTransform(
scope: TransformingLazyColumnItemScope,
): Modifier
Parameters
name | description |
---|---|
scope | The [TransformingLazyColumnItemScope] provides access to the item's index and key. |
Code Examples
TransformingLazyColumnReducedMotionSample
@Preview
@Composable
fun TransformingLazyColumnReducedMotionSample() {
var enableReduceMotion by remember { mutableStateOf(true) }
val state = rememberTransformingLazyColumnState()
AppScaffold {
ScreenScaffold(
state,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp),
modifier = Modifier.background(MaterialTheme.colorScheme.background),
edgeButton = {
EdgeButton(
onClick = { enableReduceMotion = !enableReduceMotion },
buttonSize = EdgeButtonSize.Large
) {
Text("Toggle reduce motion")
}
}
) { contentPadding ->
CompositionLocalProvider(LocalReduceMotion provides enableReduceMotion) {
TransformingLazyColumn(
state = state,
contentPadding = contentPadding,
) {
items(count = 5) {
Text(
"Text item $it",
modifier = Modifier.scrollTransform(this).animateItem()
)
}
items(count = 5) {
Button(onClick = {}, modifier = Modifier.fillMaxWidth().animateItem()) {
Text("Item $it")
}
}
}
}
}
}
}
TransformingLazyColumnScalingMorphingEffectSample
@Preview
@Composable
fun TransformingLazyColumnScalingMorphingEffectSample() {
val allIngredients = listOf("2 eggs", "tomato", "cheese", "bread")
val state = rememberTransformingLazyColumnState()
val coroutineScope = rememberCoroutineScope()
AppScaffold {
ScreenScaffold(
state,
contentPadding = PaddingValues(horizontal = 10.dp, vertical = 20.dp),
edgeButton = {
EdgeButton(onClick = { coroutineScope.launch { state.scrollToItem(1) } }) {
Text("To top")
}
}
) { contentPadding ->
TransformingLazyColumn(
state = state,
contentPadding = contentPadding,
modifier = Modifier.background(MaterialTheme.colorScheme.background)
) {
item(contentType = "header") {
// No modifier is applied - no Material 3 Motion.
ListHeader { Text("Ingredients") }
}
items(allIngredients, key = { it }) { ingredient ->
Text(
ingredient,
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyLarge,
modifier =
Modifier.fillMaxWidth()
// Apply Material 3 Motion transformations.
.scrollTransform(
this,
backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
shape = MaterialTheme.shapes.small
)
.padding(10.dp)
)
}
}
}
}
}
TransformingLazyColumnScrollingSample
@Preview
@Composable
fun TransformingLazyColumnScrollingSample() {
val state = rememberTransformingLazyColumnState()
val coroutineScope = rememberCoroutineScope()
var expandedItemKey by remember { mutableStateOf(-1) }
var elements by remember { mutableStateOf(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)) }
var nextElement = 10
fun addElement(index: Int) {
elements =
elements.subList(0, index) +
listOf(nextElement++) +
elements.subList(index, elements.count())
}
fun rainbowColor(progress: Float): Color {
val hue = progress * 360f
val saturation = 1f
val value = 1f
return Color(android.graphics.Color.HSVToColor(floatArrayOf(hue, saturation, value)))
}
AppScaffold {
ScreenScaffold(
state,
edgeButton = {
EdgeButton(
onClick = {
addElement(elements.count())
coroutineScope.launch { state.scrollToItem(elements.count() - 1) }
}
) {
Text("Add item")
}
}
) { contentPadding ->
val random = remember { Random }
TransformingLazyColumn(
state = state,
contentPadding = contentPadding,
modifier = Modifier.background(MaterialTheme.colorScheme.background)
) {
items(elements, key = { it }) {
val index = elements.indexOf(it)
Column(
modifier =
Modifier.fillMaxWidth()
.scrollTransform(
this,
backgroundColor = MaterialTheme.colorScheme.surfaceContainer,
shape = MaterialTheme.shapes.medium
)
.animateItem()
.padding(5.dp)
.clickable {
elements =
elements.subList(0, index) +
elements.subList(index + 1, elements.count())
}
) {
Row(
verticalAlignment = CenterVertically,
horizontalArrangement = spacedBy(2.dp)
) {
Text(
"Item $it",
color = MaterialTheme.colorScheme.onSurface,
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.weight(1f).fillMaxHeight()
)
Text("^", Modifier.clickable { addElement(index) })
Box(
Modifier.size(25.dp)
.drawWithContent {
drawContent()
val colorProgress =
scrollProgress?.let {
(it.topOffsetFraction + it.bottomOffsetFraction) /
2f
} ?: 0f
val r = size.height / 2f
drawCircle(
rainbowColor(colorProgress),
radius = r,
center = Offset(size.width - r, r)
)
drawCircle(
rainbowColor(random.nextFloat()),
radius = r / 8,
center = Offset(size.width - r, r)
)
}
.clickable {
expandedItemKey =
if (expandedItemKey == it) -1
else {
coroutineScope.launch { state.scrollToItem(index) }
it
}
}
)
}
AnimatedVisibility(expandedItemKey == it) {
// Expanded content goes here.
Box(modifier = Modifier.fillMaxWidth().height(100.dp))
}
}
}
}
}
}
}