This post is a journey of the steps I took while figuring out how to do a Text Shadow with Jetpack Compose. If you want to skip the journey and just get the solution, jump to the end of the post!
As of version 1.0 of Jetpack Compose, Text Shadows don’t exist in the same way they used to on TextView
. 😿
Adding a Shadow on a TextView looked like this:
<TextView
android:id="@+id/text"
style="@style/CategoryRowTitle"
tools:text="Category" />
<style name="CategoryRowTitle" parent="TextAppearance.AppCompat">
<item name="android:textSize">24sp</item>
<item name="android:textColor">@color/white</item>
<item name="android:shadowColor">@color/black</item>
<item name="android:shadowDx">4</item>
<item name="android:shadowDy">4</item>
<item name="android:shadowRadius">8</item>
</style>
Adding a Shadow to Text in Jetpack Compose
You try can put a “shadow” on your Text
Composable, but it’ll create a shadow behind the text container, not the actual characters. 🤔
Text(
text = "Fruits",
modifier = Modifier
.shadow(elevation = 2.dp)
)
Creating a Custom Shadow in Jetpack Compose
I did my best to create a shadow myself by making a copy of the text, setting it to a dark color, and offsetting it by 2.dp.
@Composable
fun TextWithShadow(
text: String,
modifier: Modifier
) {
Text(
text = text,
color = Color.DarkGray,
modifier = modifier
.offset(
x = 2.dp,
y = 2.dp
)
.alpha(0.75f)
)
Text(
text = text,
color = Color.White,
modifier = modifier
)
}
Looks great! But small differences.
Let’s Cheat and Use an AndroidView in Compose 😃
Compose has amazing interoperability with the Android View system. If something isn’t perfect in Compose, we can always just use the Android View version. Pixel perfect match! However, this wouldn’t work in Compose for Desktop because it keeps us tied to the Android View system.
@Composable
fun ComposeAndroidTextView(
text: String,
modifier: Modifier
) {
AndroidView(
modifier = modifier,
factory = { context ->
AppCompatTextView(context).apply {
setTextAppearance(R.style.ItemRowTitle)
this.text = text
}
}
)
}
Let’s Try Again with Compose… StackOverflow? 🤔
I did find this Stack Overflow post that was similar, but not exactly what I needed. Here is what it had:
val textPaintStroke = Paint().asFrameworkPaint().apply {
isAntiAlias = true
style = android.graphics.Paint.Style.STROKE
textSize = 64f
color = android.graphics.Color.BLACK
strokeWidth = 12f
strokeMiter = 10f
strokeJoin = android.graphics.Paint.Join.ROUND
}
val textPaint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
style = android.graphics.Paint.Style.FILL
textSize = 64f
color = android.graphics.Color.WHITE
}
Canvas(
modifier = Modifier.fillMaxSize(),
onDraw = {
drawIntoCanvas {
it.nativeCanvas.drawText(
"Sample",
0f,
120.dp.toPx(),
textPaintStroke
)
it.nativeCanvas.drawText(
"Sample",
0f,
120.dp.toPx(),
textPaint
)
}
}
)
What’s the Perfect Way to Match a TextView Shadow with Compose?
I’m not really sure. Update: I figured it out thanks to Antonio Leiva!
style = MaterialTheme.typography.h4.copy(
shadow = Shadow(
color = shadowColor,
offset = Offset(4f, 4f),
blurRadius = 8f
)
)
I wanted to share my journey in figuring this out, but also thank everyone in the community for helping find the “right” way to do it in compose!