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 TimeText()
uses CurvedRow()
and CurvedText()
to accomplish this,
if the code is running on a round device. Otherwise, it uses the normal Row()
and
Text()
composables,
TimeText()
is a bit overblown, particularly for a blog post, so
this sample project
has a 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 isScreenRound
property on the Configuration
, which we get via LocalConfiguration.current
.
If the screen is round, we display the current time in a CurvedText()
and wrap
that in a CurvedRow()
. 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.
The 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 270f
. anchor
is measured in degrees,
and 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
via individual CurvedText()
composables, in a CurvedRow()
with a custom anchor
value:
@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 CurvedRow()
.
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")
}
}
)
}
An anchor
of 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 anchor
values.
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, CurvedRow()
is not limited to CurvedText()
, and I hope to explore that more in a future blog post.