I'm trying to recreate Persona 5's chat using Jetpack Compose + Kotlin in Android Studio. However, I don't know how to get the y value so that the lines I'm drawing connect between avatars regardless of the message size. Could anyone more knowledgeable about Android help?
fun Modifier.drawConnectingLine(
entry1: Entry,
entry2: Entry?
): Modifier {
if (entry2 == null) return this
return drawWithCache {
val linePath = Path()
val charCount = entry1.message.text.split("\n").size.toFloat()
val bottomOffset = Offset(0f, 200+((charCount-1)*2000f))
val topLeft = entry1.lineCoordinates.leftPoint
val topRight = entry1.lineCoordinates.rightPoint
val bottomLeft = entry2.lineCoordinates.leftPoint + bottomOffset
val bottomRight = entry2.lineCoordinates.rightPoint + bottomOffset
val shadowPaint = Paint().apply {
color = android.graphics.Color.BLACK
alpha = (0.5f * 255).toInt()
maskFilter = BlurMaskFilter(4.dp.toPx(), BlurMaskFilter.Blur.NORMAL)
}
onDrawBehind {
// monta o path
with(linePath) {
rewind()
moveTo(topLeft.x, topLeft.y) // canto superior esquerdo
lineTo(topRight.x, topRight.y) // canto superior direito
lineTo(bottomRight.x, bottomRight.y) // canto inferior direito
lineTo(bottomLeft.x, bottomLeft.y) // canto inferior esquerdo
close()
}
// pontos de debug
drawCircle(Color.Green, radius = 10f, center = entry1.lineCoordinates.leftPoint)
drawCircle(Color.Blue, radius = 10f, center = entry1.lineCoordinates.rightPoint)
drawCircle(Color.Black, radius = 10f, center = entry2.lineCoordinates.leftPoint)
drawCircle(Color.White, radius = 10f, center = entry2.lineCoordinates.rightPoint)
// desenha com Paint Android nativo (com blur)
drawIntoCanvas { canvas ->
val paint = android.graphics.Paint().apply {
color = android.graphics.Color.BLACK
alpha = (0.5f * 255).toInt()
strokeWidth = 8f
style = android.graphics.Paint.Style.STROKE
strokeCap = android.graphics.Paint.Cap.ROUND
maskFilter = BlurMaskFilter(6.dp.toPx(), BlurMaskFilter.Blur.NORMAL)
}
canvas.nativeCanvas.drawPath(linePath.asAndroidPath(), paint)
}
// desenha path "puro" Compose
drawPath(
linePath,
Color.Black,
style = Fill
)
}
}
}
fun messagesToEntries(
messages: List<Message>,
avatarSize: Size,
lineWidth: Float = 80f, // largura da linha
minShift: Float = 30f, // deslocamento mínimo
maxShift: Float = 50f, // deslocamento máximo
randomBetween: (Float, Float) -> Float = { min, max ->
Random.nextFloat() * (max - min) + min
}
): List<Entry> {
return messages.mapIndexed { index, message ->
val leftX = (avatarSize.width/2f) - (lineWidth / 2f)
val rightX = leftX + lineWidth
val y = avatarSize.height / 2f
val direction = if (index % 2 == 0) 1f else -1f
val horizontalShift = randomBetween(minShift, maxShift) * direction
val lineCoordinates = LineCoordinates(
leftPoint = Offset(leftX + horizontalShift, y),
rightPoint = Offset(rightX + horizontalShift, y)
)
Entry(message, lineCoordinates)
}
}
val listState = rememberLazyListState()
LaunchedEffect(Unit)
{
viewModel.setMessages(viewModel.messages, avatarSize)
}
Box(modifier = Modifier.fillMaxSize())
{
LazyColumn (state = listState,
modifier = Modifier
.fillMaxSize(),
//.drawConnectingLines(uiState.entries),
verticalArrangement = Arrangement.spacedBy(16.dp),
)
{
items(uiState.entries.size)
{ index ->
val entry = uiState.entries[index]
val proxEntry = uiState.entries.getOrNull(index + 1)
Box(
modifier = Modifier
//.fillMaxWidth()
.then(
Modifier.drawConnectingLine(entry, proxEntry).matchParentSize() // último não desenha
)
) {
MessageRow(entry = entry, generalViewModel = viewModel)
//Não tá atualizando os valores
/*
if (prevEntry != null &&
entry.lineCoordinates.leftPoint.y != 0f &&
prevEntry.lineCoordinates.leftPoint.y != 0f
) {
DebugLineCoordinates(
entry = prevEntry,
modifier = Modifier.matchParentSize(),
color1 = Color.Blue,
color2 = Color.Green
)
}*/
HorizontalCenterDebugCircle(modifier = Modifier.matchParentSize())
}
}
}
I solved the problem by making the function that draws the messages also draw the line in the background and adding to the height y of the line coordinates the equivalent of the distance from the beginning of the message box to its center (as this is always fixed) + the total height of the box