You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现类似WhatsApp的文本与时间排版

在Jetpack Compose中实现WhatsApp风格的文本+时间排版

要实现类似WhatsApp的消息排版逻辑:短文本时时间与消息同行居右,长文本或换行文本时时间自动对齐到消息块的右下角(可在最后一行末尾或单独一行),可以通过以下两种方式实现:

方法一:使用Accompanist FlowRow(简单高效)

Accompanist库的FlowRow组件支持自动换行,能轻松实现这种流式排版效果。

步骤:

  1. 添加Accompanist FlowLayout依赖到build.gradle:
dependencies {
    implementation "com.google.accompanist:accompanist-flowlayout:<latest-version>"
}

(替换<latest-version>为当前最新版本,如0.35.0-alpha)

  1. 自定义组合函数:
import com.google.accompanist.flowlayout.FlowRow
import com.google.accompanist.flowlayout.MainAxisAlignment
import com.google.accompanist.flowlayout.CrossAxisAlignment

@Composable
fun WhatsAppStyledMessage(
    message: String,
    time: String,
    modifier: Modifier = Modifier,
    messageTextStyle: TextStyle = TextStyle.Default,
    timeTextStyle: TextStyle = TextStyle.Default.copy(color = Color.Gray)
) {
    FlowRow(
        modifier = modifier.fillMaxWidth(),
        mainAxisAlignment = MainAxisAlignment.SpaceBetween,
        crossAxisAlignment = CrossAxisAlignment.End,
        mainAxisSize = com.google.accompanist.flowlayout.SizeMode.Wrap
    ) {
        Text(
            text = message,
            style = messageTextStyle,
            modifier = Modifier.align(CrossAxisAlignment.Start)
        )
        Text(
            text = time,
            style = timeTextStyle,
            modifier = Modifier.padding(start = 8.dp)
        )
    }
}

效果说明:

  • 短文本场景:时间直接跟在消息右侧,与消息同行显示。
  • 长文本场景:当最后一行无法容纳时间时,时间自动换行并对齐到消息块右下角。
  • 换行文本场景:消息换行后最后一行仍有空间时,时间直接放在最后一行末尾。

方法二:自定义Layout组件(无额外依赖)

如果不想引入第三方库,可以通过自定义Layout组件完全控制测量和布局逻辑:

代码实现:

import androidx.compose.foundation.layout.Layout
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.Constraints

@Composable
fun MessageWithTime(
    message: String,
    time: String,
    modifier: Modifier = Modifier,
    messageTextStyle: TextStyle = TextStyle.Default,
    timeTextStyle: TextStyle = TextStyle.Default.copy(color = Color.Gray)
) {
    Layout(
        modifier = modifier,
        content = {
            Text(text = message, style = messageTextStyle)
            Text(text = time, style = timeTextStyle)
        }
    ) { measurables, constraints ->
        val messageMeasurable = measurables[0]
        val timeMeasurable = measurables[1]

        // 测量时间文本尺寸
        val timePlaceable = timeMeasurable.measure(constraints.copy(minWidth = 0))
        val timeWidth = timePlaceable.width
        val timeHeight = timePlaceable.height

        // 测量消息文本在全宽下的行数
        val fullWidthMessage = messageMeasurable.measure(constraints)
        val fullLineCount = fullWidthMessage.lineCount

        // 测量消息文本减去时间宽度后的行数,判断是否需要换行放时间
        val reducedWidthConstraints = constraints.copy(maxWidth = constraints.maxWidth - timeWidth)
        val reducedWidthMessage = messageMeasurable.measure(reducedWidthConstraints)
        val reducedLineCount = reducedWidthMessage.lineCount

        val placeTimeOnNewLine = reducedLineCount > fullLineCount
        val finalMessagePlaceable = if (placeTimeOnNewLine) fullWidthMessage else reducedWidthMessage

        // 计算整体布局宽高
        val totalWidth = maxOf(
            if (placeTimeOnNewLine) constraints.maxWidth else finalMessagePlaceable.width + timeWidth,
            timeWidth
        )
        val totalHeight = if (placeTimeOnNewLine) finalMessagePlaceable.height + timeHeight else maxOf(finalMessagePlaceable.height, timeHeight)

        // 执行布局
        layout(totalWidth, totalHeight) {
            if (placeTimeOnNewLine) {
                finalMessagePlaceable.placeRelative(0, 0)
                timePlaceable.placeRelative(totalWidth - timeWidth, finalMessagePlaceable.height)
            } else {
                finalMessagePlaceable.placeRelative(0, 0)
                // 单行消息垂直居中时间,多行消息对齐消息底部
                val timeY = if (fullLineCount == 1) {
                    (finalMessagePlaceable.height - timeHeight) / 2
                } else {
                    finalMessagePlaceable.height - timeHeight
                }
                timePlaceable.placeRelative(finalMessagePlaceable.width, timeY)
            }
        }
    }
}

使用示例:

@Composable
fun DemoMessages() {
    Column(modifier = Modifier.padding(16.dp)) {
        WhatsAppStyledMessage(
            message = "lorem ipsum dolor",
            time = "12:34 PM"
        )
        Spacer(modifier = Modifier.height(8.dp))
        WhatsAppStyledMessage(
            message = "lorem ipsum dolor....foobar",
            time = "12:30 PM"
        )
        Spacer(modifier = Modifier.height(8.dp))
        WhatsAppStyledMessage(
            message = "lorem ipsum dolor sit amet\nlorem ipsum tai bau",
            time = "12:30 PM"
        )
    }
}

内容的提问来源于stack exchange,提问作者Umar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 18:45:07