I've been trying to get text with actual borders working on Jetpack Compose.
I tried using the shadow style in the Text
composable but it doesn't work nor look very good
Text(
text = "lorem ipsum",
style = TextStyle(shadow = Shadow(color = borderColor, blurRadius = 7.5f)),
)
Only outlining the text can be accomplished with a different DrawStyle
:
Text(
text = "lorem ipsum",
style = TextStyle(
color = borderColor,
drawStyle = Stroke(),
),
)
There are various customizations available to Stroke
, take a look at its parameters.
This will, however, only draw the outline, the interior stays empty and the background will be seen instead. I don't think there's a built-in way to also fill the interior. As a workaround you could draw the Text twice, the first with the outline only, then ontop regular, filled text:
@ExperimentalComposeUiApi
@Composable
fun OutlinedText(
text: String,
modifier: Modifier = Modifier,
fillColor: Color = Color.Unspecified,
outlineColor: Color,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
overflow: TextOverflow = TextOverflow.Clip,
softWrap: Boolean = true,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current,
outlineDrawStyle: Stroke = Stroke(),
) {
Box(modifier = modifier) {
Text(
text = text,
modifier = Modifier.semantics { invisibleToUser() },
color = outlineColor,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = null,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout,
style = style.copy(
shadow = null,
drawStyle = outlineDrawStyle,
),
)
Text(
text = text,
color = fillColor,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
overflow = overflow,
softWrap = softWrap,
maxLines = maxLines,
minLines = minLines,
onTextLayout = onTextLayout,
style = style,
)
}
}
Now you can simply use this:
OutlinedText(
text = "lorem ipsum",
outlineColor = borderColor,
)
The Stroke customization for the outline can be supplied by the optional outlineDrawStyle
parameter, the fill color can be specified with fillColor
.
This new OutlinedText
should work the same as the regular Text, so you can also use all the other parameters, even the style
parameter. I am not sure how robust it will handle the modifier
parameter though, I can imagine some edge cases where this will behave differently.
To accomodate for the change in semantics (because there are now two Texts, even if it only looks like one), I hid the second text from the semantics tree. That's necessary for accessibilty (screen readers/TalkBack), instrumentation tests and so on, but I haven't actually tested its behavior.