I am making an app for the Wear OS using Jetpack Compose. I want the UI to look somewhat like this:
This is the code that I have now:
@Composable
fun DefaultPreview() {
TimeText()
ScalingLazyColumn(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
item {
Text(
text = "Title",
style = MaterialTheme.typography.title1,
color = MaterialTheme.colors.primary
)
}
item {
Text(
text = "Caption",
style = MaterialTheme.typography.caption1,
color = MaterialTheme.colors.primary
)
}
item {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Text content")
repeat(10) { Text("Line $it") }
}
}
}
}
The problem that I have with this is that when I scroll the list, the text comes underneath the TimeText
which doesn't really look that good.
So I want to make the TimeText
scroll along with the other text elements. If I put the TimeText()
as an item in the ScalingLazyColumn
, then it does work, but now there's a huge empty gap below the TimeText
which is apparently a CurvedLayout
.
How do I get rid of this empty space? Is there a better way to make the TimeText
scrollable?
The empty gap is generated due to a CurvedLayout
which is created by TimeText
, which has the dimensions equal to the size of the entire screen. Outside of a list, this wouldn't be a problem since our content would then just be superimposed on top of the CurvedLayout
. But when we use TimeText
in a list, the rest of the list items appear below TimeText
, not on top of it.
I couldn't figure out any way to make the TimeText
behave as a list item while at the same time layering the other list items on top. I also couldn't figure out any way to cut off the empty portion of TimeText
(Modifier.clip
only changes what is drawn, it doesn't change the dimensions of the composable.
So I tried a different approach, by giving a vertical offset to TimeText
which would synchronize with the scroll of the ScalingLazyColumn
. Unfortunately there is no direct way to get the absolute scroll value from a ScalingLazyListState
, so I had to resort to a hacky method by using Modifier.onGloballyPositioned
on the first item of the ScalingLazyColumn
.
@Composable
fun DefaultPreview() {
val scalingLazyListState = rememberScalingLazyListState()
var initialTimeTextOffset by remember { mutableFloatStateOf(0f) }
var timeTextOffset by remember { mutableFloatStateOf(0f) }
TimeText(Modifier.offset(y=timeTextOffset.dp))
ScalingLazyColumn(
Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally,
state = scalingLazyListState
) {
item {
Text(
text = "Title",
style = MaterialTheme.typography.title1,
color = MaterialTheme.colors.primary,
modifier = Modifier.onGloballyPositioned {
if (scalingLazyListState.centerItemIndex == 1 && scalingLazyListState.centerItemScrollOffset == 0)
initialTimeTextOffset = it.positionInRoot().y
// for some reason dividing the whole thing by 2 seems to make the animation more 'natural'
timeTextOffset = (it.positionInRoot().y - initialTimeTextOffset)/2
}
)
}
item {
Text(
text = "Caption",
style = MaterialTheme.typography.caption1,
color = MaterialTheme.colors.primary
)
}
item {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Text content")
repeat(10) { Text("Line $it") }
}
}
}
}
This approach works for me, so I will mark the question as answered for now. But I don't believe this is the 'correct' way of doing it, and this approach might also be bad performance-wise. So if anyone answers with a better approach, then I will mark that as the answer.