Can't figure out how to add a gradient to a text with an inner shadow with a modifier in Jetpack Compose.
To have something like this? Any ideas?
So far jetpack compose doesn't provide text gradient and inner shadow out of the box.
Hence need to paint it by yourself:
#Composable
fun drawGradientText(name: String, modifier: Modifier = Modifier) {
val paint = Paint().asFrameworkPaint()
val gradientShader: Shader = LinearGradientShader(
from = Offset(0f, 0f),
to = Offset(0f, 400f),
listOf(Color.Blue, Color.Cyan)
)
Canvas(modifier.fillMaxSize()) {
paint.apply {
isAntiAlias = true
textSize = 400f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
style = android.graphics.Paint.Style.FILL
color = android.graphics.Color.parseColor("#cdcdcd")
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
maskFilter = BlurMaskFilter(30f, Blur.NORMAL)
}
drawIntoCanvas { canvas ->
canvas.save()
canvas.nativeCanvas.translate(2f, 5f)
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
canvas.restore()
paint.shader = gradientShader
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
paint.maskFilter = null
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
canvas.nativeCanvas.translate(2f, 5f)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
paint.maskFilter = BlurMaskFilter(30f, Blur.NORMAL)
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
}
paint.reset()
}
}
You can adjust PorterDuff modes and offsets to meet your requirements.
Just ran into the same use case, but just for a simple gradient on text. Posting it here in case it helps someone.
What worked for me was drawing the content and then the gradient via Modifier.graphicsLayer (extrapolated from this answer on Slack):
Text(
text = "$ 20",
/** size/font style, etc. **/
modifier = Modifier.graphicsLayer(alpha = 0.99f)
.drawWithCache {
val brush = Brush.horizontalGradient(listOf(StartColor, EndColor))
onDrawWithContent {
drawContent()
drawRect(brush, blendMode = BlendMode.SrcAtop)
}
}
)
I ended up making it a Modifier for reuse:
fun Modifier.textBrush(brush: Brush) = this
.graphicsLayer(alpha = 0.99f)
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(brush, blendMode = BlendMode.SrcAtop)
}
}
Example Result:
In Jetpack Compose 1.2.0-beta01 text gradients were added.
Example:
#Composable
fun BrushDemo() {
Text(
"Brush is awesome\nBrush is awesome\nBrush is awesome",
style = TextStyle(
brush = Brush.linearGradient(
colors = RainbowColors,
tileMode = TileMode.Mirror
),
fontSize = 30.sp
)
)
}
More examples here.
Related
Since Jetpack Compose has this limitation I'm looking for workaround solution for this problem?
Maybe Canvas could do the trick? I really appreciate if someone can provide code snippets of how to render shadow in Jetpack Compose for Card, Box, Column, Row etc utilising additional parameters like X and Y offset, blur and opacity with custom implementation (Canvas or something else)?
I managed to find solution thanks to this code snippets
fun Modifier.advancedShadow(
color: Color = Color.Black,
alpha: Float = 1f,
cornersRadius: Dp = 0.dp,
shadowBlurRadius: Dp = 0.dp,
offsetY: Dp = 0.dp,
offsetX: Dp = 0.dp
) = drawBehind {
val shadowColor = color.copy(alpha = alpha).toArgb()
val transparentColor = color.copy(alpha = 0f).toArgb()
drawIntoCanvas {
val paint = Paint()
val frameworkPaint = paint.asFrameworkPaint()
frameworkPaint.color = transparentColor
frameworkPaint.setShadowLayer(
shadowBlurRadius.toPx(),
offsetX.toPx(),
offsetY.toPx(),
shadowColor
)
it.drawRoundRect(
0f,
0f,
this.size.width,
this.size.height,
cornersRadius.toPx(),
cornersRadius.toPx(),
paint
)
}
}
Based on the post above, I've changed the implementation to match the parameters of the site below
https://html-css-js.com/css/generator/box-shadow/
My current implementation is the following
internal fun Modifier.coloredShadow(
color: Color = Color.Black,
borderRadius: Dp = 0.dp,
blurRadius: Dp = 0.dp,
offsetY: Dp = 0.dp,
offsetX: Dp = 0.dp,
spread: Float = 0f,
modifier: Modifier = Modifier,
) = this.then(
modifier.drawBehind {
this.drawIntoCanvas {
val paint = Paint()
val frameworkPaint = paint.asFrameworkPaint()
val spreadPixel = spread.dp.toPx()
val leftPixel = (0f - spreadPixel) + offsetX.toPx()
val topPixel = (0f - spreadPixel) + offsetY.toPx()
val rightPixel = (this.size.width + spreadPixel)
val bottomPixel = (this.size.height + spreadPixel)
if (blurRadius != 0.dp) {
/*
The feature maskFilter used below to apply the blur effect only works
with hardware acceleration disabled.
*/
frameworkPaint.maskFilter =
(BlurMaskFilter(blurRadius.toPx(), BlurMaskFilter.Blur.NORMAL))
}
frameworkPaint.color = color.toArgb()
it.drawRoundRect(
left = leftPixel,
top = topPixel,
right = rightPixel,
bottom = bottomPixel,
radiusX = borderRadius.toPx(),
radiusY = borderRadius.toPx(),
paint
)
}
}
)
Feel free to add comments and help to evolve this gist
https://gist.github.com/hernandazevedo/dfd41b39d0156c740a195f6f5866ce20
I've recently created shadow modifier allowing to set both radius and offsets.
#Immutable
data class Shadow(
#Stable val offsetX: Dp,
#Stable val offsetY: Dp,
#Stable val radius: Dp,
#Stable val color: Color,
)
fun Modifier.withShadow(
shadow: Shadow,
shape: Shape,
) = drawBehind {
drawIntoCanvas { canvas ->
val paint = Paint()
paint.asFrameworkPaint().apply {
this.color = Color.Transparent.toArgb()
setShadowLayer(
radius = shadow.radius.toPx(),
dx = shadow.offsetX.toPx(),
dy = shadow.offsetY.toPx(),
shadowColor = shadow.color,
)
}
val outline = shape.createOutline(size, layoutDirection, this)
canvas.drawOutline(outline, paint)
}
}
I am learning Jetpack Compose and would like to build something like this
I have tried using Box layout by stacking CircularProgressIndicator but requires hardcoding the circle sizes. I want the rings to be size agnostic.
How do I achieve this using Compose?
You can try to do with Canvas. I did this and could give you a start point to achieve what you want...
#Composable
fun DrawGradientCircles() {
Canvas(
modifier = Modifier
.size(300.dp)
.background(Color.Gray)
) {
drawCircle(
brush = Brush.sweepGradient(listOf(Color.Magenta, Color.Red)),
radius = 300f,
style = Stroke(90f)
)
drawCircle(
brush = Brush.sweepGradient(listOf(Color.Green, Color.Yellow)),
radius = 200f,
style = Stroke(90f)
)
drawCircle(
brush = Brush.sweepGradient(listOf(Color.Cyan, Color.Blue)),
radius = 100f,
style = Stroke(90f)
)
}
}
This is the result:
EDIT: I posted an updated version here:
https://gist.github.com/nglauber/e947dacf50155fb72408e83f6595e430
Hope it helps.
I was able to accomplish it using CircularProgressIndicator
#Composable
fun ringView(){
var sz by remember { mutableStateOf(Size.Zero)}
Box(
Modifier
.aspectRatio(1f)
.fillMaxSize()
.background(Color.Blue)
.onGloballyPositioned { coordinates ->
sz = coordinates.size.toSize()
}
, contentAlignment = Alignment.Center){
Box(Modifier.aspectRatio(1f), contentAlignment = Alignment.Center){
Text(text = pxToDp(sz.height.toInt()).toString())
CircularProgressIndicator(progress = 0.9F, Modifier.size(pxToDp(sz.width.toInt()).dp), strokeWidth = (pxToDp(sz.width.toInt())/15).dp,color = Color.Green)
CircularProgressIndicator(progress = 0.9F, Modifier.size((pxToDp(sz.width.toInt())-(2*(pxToDp(sz.width.toInt())/15))).dp), strokeWidth = (pxToDp(sz.width.toInt())/15).dp, color = Color.Black )
CircularProgressIndicator(progress = 0.9F, Modifier.size((pxToDp(sz.width.toInt())-(4*(pxToDp(sz.width.toInt())/15))).dp), strokeWidth = (pxToDp(sz.width.toInt())/15).dp, color = Color.Gray )
CircularProgressIndicator(progress = 0.9F, Modifier.size((pxToDp(sz.width.toInt())-(6*(pxToDp(sz.width.toInt())/15))).dp), strokeWidth = (pxToDp(sz.width.toInt())/15).dp, color = Color.Cyan )
CircularProgressIndicator(progress = 0.9F, Modifier.size((pxToDp(sz.width.toInt())-(8*(pxToDp(sz.width.toInt())/15))).dp), strokeWidth = (pxToDp(sz.width.toInt())/15).dp, color = Color.Magenta )
}
}
}
fun pxToDp(px: Int): Int {
return (px / Resources.getSystem().displayMetrics.density).toInt()
}
I have a Jetpack Compose Text() element that I'd like to outline in black like so .
Anyone know how to do this?
I've tried using the border() modifier, but that just adds a border around the rectangular area containing the text. I've also tried overlaying two text elements, but that doesn't quite work either.
The 1.4.0-alpha01 introduced a DrawStyle parameter to TextStyle function that enables drawing outlined text.
You can use something like:
Text(
text = "Sample",
style = TextStyle.Default.copy(
fontSize = 64.sp,
drawStyle = Stroke(
miter = 10f,
width = 5f,
join = StrokeJoin.Round
)
)
)
Before 1.4.0-alpha01 you can use a Canvas and the drawIntoCanvas function.
Something like:
Canvas(
modifier = Modifier.fillMaxSize(),
onDraw = {
drawIntoCanvas {
it.nativeCanvas.drawText(
"Sample",
0f,
120.dp.toPx(),
textPaintStroke
)
it.nativeCanvas.drawText(
"Sample",
0f,
120.dp.toPx(),
textPaint
)
}
}
)
with these Paint:
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
}
I can easily create a normal border using the Modifier.border() but how to create a dashed border as shown in the image below.
There isn't a parameter in Modifier.border() to achieve a dashed path.
However you can use a DrawScope to draw a dashed Path using PathEffect.dashPathEffect.
Something like:
val stroke = Stroke(width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
You can draw it using the drawBehind modifier:
Box(
Modifier
.size(250.dp,60.dp)
.drawBehind {
drawRoundRect(color = Color.Red, style = stroke)
},
contentAlignment = Alignment.Center
) {
Text(textAlign = TextAlign.Center,text = "Tap here to introduce yourseft")
}
If you want rounded corner just use the cornerRadius attribute in the drawRoundRect method:
drawRoundRect(color = Color.Red,style = stroke, cornerRadius = CornerRadius(8.dp.toPx()))
If you prefer you can build your custom Modifier with the same code above. Something like:
fun Modifier.dashedBorder(strokeWidth: Dp, color: Color, cornerRadiusDp: Dp) = composed(
factory = {
val density = LocalDensity.current
val strokeWidthPx = density.run { strokeWidth.toPx() }
val cornerRadiusPx = density.run { cornerRadiusDp.toPx() }
this.then(
Modifier.drawWithCache {
onDrawBehind {
val stroke = Stroke(
width = strokeWidthPx,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
drawRoundRect(
color = color,
style = stroke,
cornerRadius = CornerRadius(cornerRadiusPx)
)
}
}
)
}
)
and then just apply it:
Box(
Modifier
.size(250.dp,60.dp)
.dashedBorder(1.dp, Red, 8.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "Tap here to introduce yourself",
textAlign = TextAlign.Center,
)
}
After some digging in the normal border modifier, I found out that it uses Stroke object which can take a parameter PathEffect that can make it dashed, here is a modified version of the normal border function that takes this parameter.
https://gist.github.com/DavidIbrahim/236dadbccd99c4fd328e53587df35a21
I wrote this extension for the Modifier you can simply use it or modify it.
fun Modifier.dashedBorder(width: Dp, radius: Dp, color: Color) =
drawBehind {
drawIntoCanvas {
val paint = Paint()
.apply {
strokeWidth = width.toPx()
this.color = color
style = PaintingStyle.Stroke
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
}
it.drawRoundRect(
width.toPx(),
width.toPx(),
size.width - width.toPx(),
size.height - width.toPx(),
radius.toPx(),
radius.toPx(),
paint
)
}
}
Can't figure out how to add a gradient to a text with an inner shadow with a modifier in Jetpack Compose.
To have something like this? Any ideas?
So far jetpack compose doesn't provide text gradient and inner shadow out of the box.
Hence need to paint it by yourself:
#Composable
fun drawGradientText(name: String, modifier: Modifier = Modifier) {
val paint = Paint().asFrameworkPaint()
val gradientShader: Shader = LinearGradientShader(
from = Offset(0f, 0f),
to = Offset(0f, 400f),
listOf(Color.Blue, Color.Cyan)
)
Canvas(modifier.fillMaxSize()) {
paint.apply {
isAntiAlias = true
textSize = 400f
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
style = android.graphics.Paint.Style.FILL
color = android.graphics.Color.parseColor("#cdcdcd")
xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
maskFilter = BlurMaskFilter(30f, Blur.NORMAL)
}
drawIntoCanvas { canvas ->
canvas.save()
canvas.nativeCanvas.translate(2f, 5f)
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
canvas.restore()
paint.shader = gradientShader
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
paint.maskFilter = null
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
canvas.nativeCanvas.translate(2f, 5f)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER)
paint.maskFilter = BlurMaskFilter(30f, Blur.NORMAL)
canvas.nativeCanvas.drawText(name, 0f, 400f, paint)
}
paint.reset()
}
}
You can adjust PorterDuff modes and offsets to meet your requirements.
Just ran into the same use case, but just for a simple gradient on text. Posting it here in case it helps someone.
What worked for me was drawing the content and then the gradient via Modifier.graphicsLayer (extrapolated from this answer on Slack):
Text(
text = "$ 20",
/** size/font style, etc. **/
modifier = Modifier.graphicsLayer(alpha = 0.99f)
.drawWithCache {
val brush = Brush.horizontalGradient(listOf(StartColor, EndColor))
onDrawWithContent {
drawContent()
drawRect(brush, blendMode = BlendMode.SrcAtop)
}
}
)
I ended up making it a Modifier for reuse:
fun Modifier.textBrush(brush: Brush) = this
.graphicsLayer(alpha = 0.99f)
.drawWithCache {
onDrawWithContent {
drawContent()
drawRect(brush, blendMode = BlendMode.SrcAtop)
}
}
Example Result:
In Jetpack Compose 1.2.0-beta01 text gradients were added.
Example:
#Composable
fun BrushDemo() {
Text(
"Brush is awesome\nBrush is awesome\nBrush is awesome",
style = TextStyle(
brush = Brush.linearGradient(
colors = RainbowColors,
tileMode = TileMode.Mirror
),
fontSize = 30.sp
)
)
}
More examples here.