Compose for Wear: CurvedRow() and CurvedText()
Compose UI is not just for phones, tablets, foldables, notebooks, and desktops.
Compose UI is for watches as well, via the Compose for Wear set of libraries.
(Google calls it “Wear Compose” on that page, but that just makes me think
“Wear Compose? There! There Compose!”).
(and, yes, I’m old)
Compose for Wear has a bunch of composables designed for the watch experience.
In particular, Compose for Wear has support for having content curve to match
the edges of a round Wear OS device.
The Compose for Wear edition of
Scaffold() has a
timeText parameter. This
is a slot API, taking a composable as a value, where typically you will see that
composable delegate purely to
TimeText(). That gives you the current time
across the top of the watch screen, including curving that time on round screens:
The implementation of
CurvedText() to accomplish this,
if the code is running on a round device. Otherwise, it uses the normal
TimeText() is a bit overblown, particularly for a blog post, so
this sample project
SimpleTimeText() composable with a subset of the functionality:
@ExperimentalWearMaterialApi @Composable fun SimpleTimeText( modifier: Modifier = Modifier, timeSource: TimeSource = TimeTextDefaults.timeSource(TimeTextDefaults.timeFormat()), timeTextStyle: TextStyle = TimeTextDefaults.timeTextStyle(), contentPadding: PaddingValues = PaddingValues(4.dp) ) val timeText = timeSource.currentTime if (LocalConfiguration.current.isScreenRound) CurvedRow(modifier.padding(contentPadding)) CurvedText( text = timeText, style = CurvedTextStyle(timeTextStyle) ) else Row( modifier = modifier .fillMaxSize() .padding(contentPadding), verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.Center ) Text( text = timeText, style = timeTextStyle, )
We can determine whether or not the screen is round from the
property on the
Configuration, which we get via
If the screen is round, we display the current time in a
CurvedText() and wrap
that in a
CurvedText() knows how to have the letters of the text
follow the curve of the screen, and
CurvedRow() knows how to have child composables
follow the curve of the screen.
timeText slot parameter in
Scaffold() puts the time at the top of the
screen by default. That position is controlled by the
anchor parameter to
CurvedRow(), where the default
anchor is measured in degrees,
270f is the value for the top of the screen (probably for historical reasons).
SampleRow() in that sample project lets us display multiple separate strings
CurvedText() composables, in a
CurvedRow() with a custom
@Composable private fun SampleRow(anchor: Float, modifier: Modifier, vararg textBits: String) CurvedRow( modifier = modifier.padding(4.dp), anchor = anchor ) textBits.forEach CurvedText(it, modifier = Modifier.padding(end = 8.dp))
SampleRow() accepts a
Modifier and tailors it to add a bit of padding to the
We can then use
SampleRow() to display text in other positions on the screen:
@ExperimentalWearMaterialApi @Composable fun MainScreen() Scaffold( timeText = SimpleTimeText() , content = Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) Text(text = "Hello, world!") SampleRow(anchor = 180f, modifier = Modifier.align(Alignment.CenterStart), "one", "two", "three") SampleRow(anchor = 0f, modifier = Modifier.align(Alignment.CenterEnd), "uno", "dos", "tres") SampleRow(anchor = 90f, modifier = Modifier.align(Alignment.BottomCenter), "eins", "zwei", "drei") )
0f is the end edge of the screen,
90f is the bottom, and
180f is the start edge.
Note that we also use
align() to control the positioning within the
Box(), with values
that line up with our chosen
The result is that we have text on all four edges, plus a centered “Hello, world!”:
CurvedRow() does not handle consecutive bits of
CurvedText() all that well —
ideally, use a single
CurvedText() with the combined text. However,
is not limited to
CurvedText(), and I hope to explore that more in a future blog post.