I have an interesting design to implement. Have a ...
# compose
m
I have an interesting design to implement. Have a LazyColumn which can consist of any number of items but as whole this list should be shown on a card. So it should have rounded corners at the top and bottom and also a border along the whole thing. I visualized it on a screenshot - the red lines delimit LazyColumn items. I will probably need to draw these backgrounds directly on a Canvas, but maybe you can think of a more high-level way of doing that? I can't think of any - the border always outlines the whole shape, can't restrict it to just a part of a shape.
✔️ 1
n
They always but those design in my company, I always see it's not possible, I'm very interested if there is a way to do that in compose
(which is basically having a recycled List with a "card" background)
m
exactly
c
Could you provide the whole screenshot? It looks clipped
Overall,
LazyColumn
can be within anything else like
Surface
or another
Row/Box/Column
which you can add modifiers to in order to draw a background with a shape
☝🏼 1
If you want to draw a border on only certain sides of the shape, this custom modifier may help you:
Copy code
fun Modifier.upperRoundRectStroke(
    brush: Brush,
    stroke: Stroke,
    cornerRadius: CornerRadius
): Modifier {
    return this.then(Modifier.drawWithContent {
        drawContent()
        clipRect {
            drawRoundRect(
                brush = brush,
                topLeft = Offset(stroke.width / 2, stroke.width / 2), // inset round rect to account for the stroke width
                size = Size(
                    size.width - stroke.width, // adjust size to account for stroke width on either side
                    size.height - stroke.width / 2 + cornerRadius.y // make the rect taller to extend beyond the bounds of the clip
                ),
                style = stroke,
                cornerRadius = cornerRadius
            )
        }
    }
    )
}
🙏 1
m
The problem with it being in a surface is that the surface / card will only e.g. fillMaxHeight and the lazycolumn will scroll within. The effect I'm looking for is that the card background is arbitrarily long.
This modifier looks promising though, I will give it a try, thank you @Chris Sinco [G]!
Or do you say that I can make my Surface as long as the LazyColumn and its content and still get the benefits of recycling somehow? It wasn't possible with recyclerview but maybe it is with compose?
c
It’s a good question - I don’t know if the recycling behavior will still work if you set a modifier to
wrapContentHeight()
on the
LazyColumn
within a
Surface
or
Card
that has vertical scrolling. @Andrey Kulikov may know more
👍 1
a
yeah, there is no good way to achieve what you want automatically. you indeed have to split the shape and draw parts of the shape separately for every items. how would you solve it with RecyclerView? I think most people will try to add some item decorators, which is pretty much the same as adding draw modifiers on separate items
m
Yeah, exactly the same with recycler. I was rather thinking about something like BorderStroke but masked at one side and I got something very similar from Chris, so that's probably gonna work just fine. Will update :) thank you guys!
https://gist.github.com/micHar/3926308e9467bac806f15f615b8a9c72 Works great, thank you @Chris Sinco [G]! If you have any optimization hints, I'd be happy to hear them
🙏 1
c
Awesome! I actually got that snippet from @Nader Jawad who specifically works on graphics so he may have more tips as well 🙂
n
I took a quick look at the code snippet. Looks reasonable to me. One thing to note is to use the DrawScope method overloads that accept colors as inputs to avoid allocating a brush. Colors are implemented as inline classes in compose and are effectively primitives around a long. Additionally favor usage of
Modifier.drawWithCache
if you have objects that need to be allocated with sizing information. For example the stroke parameter would benefit from being cached across calls. As it is implemented now, a new stroke object will be allocated on every draw operation. The implementation would be identical to that of the existing
Modifier.drawWithContent
implementation except the objects could be allocated within the main lambda block and the rest of the implementation would be placed in an
onDraw
block
m
Thanks @Nader Jawad, I updated the gist to add cache (and refactored it a bit to avoid repetition). I couldn't quite find how to us the overloaded DrawScope, but I figured that if I can cache the strokes and cornerradius, I can cache the Brush too, right? Thanks you guys a ton for your help!
n
Looks like you used it properly. Yes you can cache anything within the
Modifier.drawWithCache
lambda. It will automatically get invalidated whenever the size of the composable changes or any state parameters that are read in that block also change
🙏 2