is it possible to draw border on the outside of th...
# compose
p
is it possible to draw border on the outside of the composable, rather than on the inside? I have an use case, when border might be visible or not. And I would like to better align it, without making component jump.
Copy code
@Preview
@Composable
fun Preview() {
    var isBorderVisible = false
    Box(modifier = Modifier
        .background(Color.White)
        .size(80.dp)
        .let {
            if (isBorderVisible) {
                it.border(color = Color.Magenta, width = 20.dp)
            } else {
                it
            }
        }
    )
}
j
To not jump the ui use
Color.Transparent
v
That will still participate in the layout if you need a padding between the content and the border.
p
@jamshedalamqaderi yeah, but that makes aligning a bit more annoying, since I'm aligning it to the border. And you need to take this into account everywhere you use this composable
maybe something similar to how shadow works maybe?
v
I imagine you're looking for something that draws a border outside the actual bounds of the layout with some padding?
p
correct yeah
if possible
v
do you need to support arbitrary shapes?
you can just use
Modifier.drawWithCache
to draw in arbitrary coordinates, just need to calculate them right
🤔 1
p
only a rounded border
v
you can take a look at how
Modifier.border()
draws it
p
ok, thanks, will check this out
a
I'd do
Copy code
drawWithContent()
drawRect(style = Stroke(width))
That's how Modifier.border also does it, except via ModifierNodeElement
You can put size/color/etc calculations in cache
v
here's a simplified version of my own implementation, unfortunately it's not public so I can't share completely, but you should get the basic idea from this:
Copy code
val Padding = // const
val Thickness = // const

fun Modifier.outsetBorder(shape: Shape) = drawWithCache {
    val outline = shape.createOutline(size, layoutDirection, this)
    val cornerRadius = when {
        outline is Outline.Rounded && outline.roundRect.isSimple -> outline.roundRect.topLeftCornerRadius
        else -> CornerRadius.Zero
    }

    val borderSize = [...]
    val expandedCornerRadius = [...]
    val topLeft = [...]

    onDrawWithContent {
        drawContent()
        drawRoundRect(
            color = [...],
            topLeft = topLeft,
            size = borderSize,
            cornerRadius = expandedCornerRadius,
            style = [...]
        )
    }
}
👍 1
e
.padding(size).border().padding(-size)
would be easier, wouldn't it?
oh I have the signs flipped but basically that
v
negative padding is illegal
Negative padding is not permitted — it will cause IllegalArgumentException. See Modifier.offset.
e
ah right, last time I implemented negative padding with a custom layout
then yeah, custom draw is probably the most straightforward solution
j
On my project i'm doing like this and it is not resized on selected.
Copy code
Modifier
            .background(
                if (selected) White else Color.Transparent,
                DefaultCornerShape
            )
            .then(
                if (!selected) Modifier.border(
                    1.dp,
                    if (isError) MaterialTheme.colorScheme.error else White.copy(
                        alpha = 0.3f
                    ),
                    DefaultCornerShape
                ) else Modifier
            )
            .then(modifier)
v
the issue is if you want some padding between the element and the outset border, then your actual content would have to be smaller to accommodate the padding
👍 1
p
But
drawWithCache
will still clip composable to it's measured rect size right? So basically, if you want to have border visible, you have to always set padding to the component, right?
e
no
v
no, it doesn't clip
e
ComposeView will clip at its size, but compose itself defaults to not clipping at bounds
v
(unless you use
Modifier.clip()
or
Modifier.graphicsLayer { clip = true }
in a parent)
p
hm, I have following implemented. If I use it in the Column composable, my rectangle border is clipped
Copy code
@Preview
@Composable
private fun Preview() {
    Column {
        MyComposable()
    }
}

@Composable
private fun MyComposable() {
    Box(modifier = Modifier
//        .padding(40.dp)
        .background(Color.White)
        .size(80.dp)
        .drawWithCache {
            val border = 20.dp.toPx()
            onDrawWithContent {
                drawContent()
                drawRoundRect(
                    topLeft = Offset(-border / 2, -border / 2),
                    size = Size(
                        width = size.width + border,
                        height = size.height + border
                    ),
                    color = Color.Magenta,
                    style = Stroke(width = border)
                )
            }
        }
    )
}
v
you need to have a bit of space around your composable in the preview because the preview window will happily clip everything blob smile
try
Column(Modifier.padding(10.dp)
in the preview
p
yeah, that was I wondering before. I guess that's only the case for preview then?
e
preview and other things that bridge compose to the outside world (e.g. ComposeView)
p
thank you for helping guys 🍺
529 Views