constraintlayout
constraintlayout copied to clipboard
[Compose] [MotionLayout] Text with animated fontSize returns to its original bounding box after recomposition?
I have the following ConstraintSet
s for the MotionLayout
:
@Composable
private fun StartConstraintSet() = ConstraintSet(
""" {
title: {
top: ['parent', 'top'],
start: ['parent', 'start'],
end: ['parent', 'end'],
bottom: ['content', 'top', 24],
custom: {
fontSize: 32,
fontWeight: 400
}
},
options: {
end: ['parent', 'end', 0],
bottom: ['content', 'top', 15]
},
content: {
bottom: ['parent', 'bottom', 0]
}
} """
)
@Composable
private fun EndConstraintSet() = ConstraintSet(
""" {
title: {
top: ['parent', 'top', 0],
start: ['parent', 'start', 16],
bottom: ['content', 'top', 0],
custom: {
fontSize: 20,
fontWeight: 500
}
},
options: {
end: ['parent', 'end', 0],
bottom: ['content', 'top', 0],
top: ['parent', 'top', 0]
},
content: {
bottom: ['parent', 'bottom', 0]
}
} """
)
and the following scene:
MotionLayout(
start = StartConstraintSet(),
end = EndConstraintSet(),
progress = if (swipingState.progress.to == SwipingStates.COLLAPSED) swipingState.progress.fraction else 1f - swipingState.progress.fraction,
modifier = Modifier
.fillMaxWidth()
.height(height)
) {
Text(
text = title,
modifier = Modifier
.layoutId("title")
.wrapContentWidth(unbounded = true) // This is necessary because title's last letter was being clipped in both start and end
.wrapContentHeight(),
color = MaterialTheme.colors.onSurface,
fontWeight = FontWeight(motionInt("title", "fontWeight")),
fontSize = motionFontSize("title", "fontSize")
)
OptionsRow(
options = options,
modifier = Modifier
.layoutId("options")
.fillMaxWidth(.5f)
.padding(end = 16.dp)
)
content(
Modifier
.layoutId("content")
)
}
This is a correctly rendered StartConstraintSet
:
And this is a correctly rendered
EndConstraintSet
:
However, after the whole app bar is recomposed, title
obtains what looks like a margin:
This doesn't happen when simply scaling the text, so it must be its bounding box returning to 32sp
value from before the transformation. I cannot explain this behavior with anything else.
Which version of the library are you using?
Have you tried removing the wrapContentWidth/Height modifiers? Ie: Just Modifier.layoutId("title")
MotionLayout will default to wrapContent.
I am using constraintlayout-compose:1.0.0
.
I put wrapContentWidth
on the text because without it another problem arises: the text is being inconsistently clipped.
Here I assigned "Long text" as title's value:
It is rendered correctly after recomposition. I believe it belongs to a separate issue.
But yes, margin does not appear if I remove wrapContentWidth
.
Taking another look at this, not sure if this is what you intended but it seems to do the trick in terms of the expected layout.
@Preview
@Composable
private fun Issue507Preview() {
var toEnd by remember { mutableStateOf(false) }
val progress by animateFloatAsState(
targetValue = if(toEnd) 1f else 0f,
tween(2500)
)
Column {
Button(onClick = {
toEnd = !toEnd
}) {
Text(text = "Run")
}
Issue507(progress = progress)
}
}
@OptIn(ExperimentalMotionApi::class)
@Composable
fun Issue507(progress: Float) {
MotionLayout(
modifier = Modifier
.background(Color.LightGray)
.fillMaxWidth()
.height(lerp(150.dp, 50.dp, progress)),
motionScene = MotionScene(content = """
{
ConstraintSets: {
start: {
title: {
top: ['parent', 'top'],
start: ['parent', 'start'],
end: ['parent', 'end'],
bottom: ['options', 'top', 24],
custom: {
fontSize: 32,
fontWeight: 400
}
},
options: {
end: ['parent', 'end', 0],
bottom: ['content', 'top', 15]
},
content: {
width: 'spread', height: 'wrap',
start: ['parent', 'start'],
end: ['parent', 'end'],
bottom: ['parent', 'bottom', 0]
}
},
end: {
title: {
top: ['parent', 'top', 0],
start: ['parent', 'start', 16],
bottom: ['content', 'top', 0],
custom: {
fontSize: 20,
fontWeight: 500
}
},
options: {
end: ['parent', 'end', 0],
bottom: ['content', 'top', 0],
top: ['parent', 'top', 0]
},
content: {
width: 'spread', height: 'wrap',
start: ['parent', 'start'],
end: ['parent', 'end'],
bottom: ['parent', 'bottom', 0]
}
}
},
Transitions: {
default: {
from: 'start',
to: 'end'
}
}
}
""".trimIndent()),
progress = progress
) {
Text(
text = "This is a very long text",
modifier = Modifier
.layoutId("title")
.wrapContentWidth(unbounded = true), // Seems to measure the text more accurately
color = MaterialTheme.colors.onSurface,
maxLines = 1, // maxLines and no softWrap help a bit with clipping
softWrap = false,
fontWeight = FontWeight(motionInt("title", "fontWeight")),
fontSize = motionFontSize("title", "fontSize")
)
Options(
modifier = Modifier
.layoutId("options")
.background(Color.Blue)
)
Tabs(
Modifier
.layoutId("content")
.background(Color.Red)
)
}
}
@Composable
private fun Options(modifier: Modifier = Modifier) {
Row(modifier = modifier) {
Icon(imageVector = Icons.Default.Settings, contentDescription = null)
Icon(imageVector = Icons.Default.Notifications, contentDescription = null)
Icon(imageVector = Icons.Default.Filter, contentDescription = null)
Icon(imageVector = Icons.Default.Search, contentDescription = null)
}
}
@Composable
private fun Tabs(modifier: Modifier = Modifier) {
Row(modifier = modifier,horizontalArrangement = Arrangement.SpaceEvenly) {
Text(text = "All")
Text(text = "My")
}
}
There seems to be two issues while animating the text that results in the clipping.
- Text bounds not calculated properly during animation
- At some point during the animation the location of the starting position seems to collapse to 0,0 instead of staying around the middle of the layout
In any case, thanks for the report, at least for the clipping I understand a little better what's happening.