Anyone with an idea of how to achieve overlapping ...
# compose
j
Anyone with an idea of how to achieve overlapping avatars?
a
This will definitely help you. @Leland Richardson [G] actually demoed this exact component at one point using a custom offset
Modifier
. https://github.com/lelandrichardson/compose-dogfooding/blob/main/project-management-app/app/src/main/java/com/example/pmapp/TimelineScreen.kt#L302 And if you want to watch him working on it, starts around here:

https://youtu.be/FEBlFU1SB04?list=PLcgGtmZOsTwErUfFxtjLWAyzes1Oj7wzo&t=5494β–Ύ

h
You can also do it easily with a custom layout that places avatars in reverse order with expected placement.
πŸ‘ 2
πŸ‘πŸ½ 1
d
Maybe draw the junction by making the preceding circle transparent along the path?
You may need to manually draw these on a canvas
i
Rude way is use row and Spacers
d
How would you get that to work with the transparency requirement @iamthevoid ? The spacer would have to make the thing behind it transparent
i
Until transparency needed it can be ignored :) i answered to the @Jeff's question, not to the @Chachako's
😊 2
h
Why not just clip them to a custom shape? You can draw any shape you want
βž• 2
n
Here's a sample composable that will generate an output similar to what @Chachako was sharing above:
Copy code
@Composable
fun TransparentChipDemo() {
    val backgroundGradient = Brush.linearGradient(
        0.0f to Color.Red,
        1.0f to Color.Blue
    )
    val circleCount = 5
    val circleColors = listOf(Color.Green, Color.Yellow, Color.Cyan, Color.Magenta, Color.LightGray)
    val clearStroke = Stroke(width = 5.0f)
    Box(modifier = Modifier.size(300.dp, 150.dp)
        .background(backgroundGradient)
        .graphicsLayer {
            // Slight alpha to force compositing layer
            // This is necessary to create an offscreen buffer to draw contents into
            // and use the clear blend mode to crop out the content around the circle chips
            // that are drawn
            // Future versions of compose will have an explicit flag to force compositing to
            // an offscreen buffer but for now include a small alpha to force it
            alpha = 0.99f
        }
        .drawBehind {
            val circleSize = (size.minDimension / circleCount)
            val circleRadius = circleSize / 2
            var centerX = size.width / 2 - (circleRadius * (circleCount + 1)) / 2 + circleRadius
            for (i in 0 until circleCount) {
                val circleOffset = Offset(centerX, size.height / 2)
                drawCircle(circleColors[i], circleRadius, circleOffset)
                drawCircle(
                    Color.Black,
                    circleRadius,
                    circleOffset,
                    style = clearStroke,
                    blendMode = BlendMode.Clear
                )
                centerX += circleRadius
            }
        }
    )
}
😎 1
In the cast of drawing circles of pngs I recommend creating a
ShaderBrush
using
ImageShader
with the given profile image and replacing the
drawCircle(circleColors[i]...
call above with that brush instead. I left it as circles here for the sake of example.
πŸ™πŸ½ 1
I refactored this a bit and came up with a more generic solution that will apply a circular clip + mask for any composable. I'm using the
Image
composable in this sample for demo sake but you insert any composable within
Chip
to get a similar result:
Copy code
@Composable
fun ChipStack(modifier: Modifier = Modifier) {
    val size = 80.dp
    val sizeModifier = Modifier.size(size)
    val colors = listOf(Color.Green, Color.Yellow, Color.Cyan, Color.Magenta, Color.LightGray)
    val width = (size / 2) * (colors.size + 1)
    Box(modifier = modifier.size(width, size)
            .graphicsLayer {
                alpha = 0.99f // slight alpha to force compositing layer
            },
    ) {
        var offset = 0.dp
        for (color in colors) {
            Chip(strokeWidth = 10.0f, sizeModifier.offset(offset)) {
                Image(ColorPainter(color), contentDescription = null)
            }
            offset += size / 2
        }
    }
}

@Composable
fun Chip(
    strokeWidth: Float,
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit
) {
    val stroke = remember(strokeWidth) {
        Stroke(width = strokeWidth)
    }
    Box(modifier = modifier
        .drawWithContent {
            drawContent()
            drawCircle(
                Color.Black,
                size.minDimension / 2,
                size.center,
                style = stroke,
                blendMode = BlendMode.Clear
            )
        }.graphicsLayer {
            clip = true
            shape = CircleShape
        }
    ) {
        content()
    }
}

@Composable
fun ChipStackDemo() {
    Box(modifier = Modifier.fillMaxSize().wrapContentSize()) {
        val backgroundGradient = remember {
            Brush.linearGradient(
                0.0f to Color.Red,
                1.0f to Color.Blue
            )
        }
        ChipStack(Modifier.background(backgroundGradient))
    }
}
πŸ‘ 6
This snippet above generates a similar output
c
@Nader Jawad This is really great! Thank you
πŸ‘ 1
j
Thank you @Nader Jawad!
πŸ‘ 2
b
Clutch! Needed this today
n
CC @Rebecca Franks
l
very nice!
n
Following up on this thread. The
alpha = 0.99f
workaround in the code snippet above is no longer necessary. With compose 1.4 you can specify
CompositingStrategy.Always
to force rendering of a
graphicsLayer
into an off screen buffer for masking purposes
c
@Nader Jawad I'm using 1.4.1 and it doesn't seem to have a CompositingStrategy.Always option. Which artifact specifically needs to be using 1.4?
n
Should be in the compose:ui package
The change landed late last year so if folks have updated since the 1.4.0 alpha02 release they should see this API
c
yeah, it looks like CompositingStrategy.Always was renamed. but i was confused, because i didn't know that. lol
n
Oh that's right it's
CompositingStrategy.Offscreen
now. "Always" was the original name
c
Yeah. lol. thanks though this was super helpful. Offscreen was the missing piece for me