Reputation: 815
Tried to implement Text
with timestamp. Found that easiest way it's use inlineContent
. But it doesn't work well with overflow = TextOverflow.Ellipsis
@Composable
fun TextWithTimestamp(
text: String,
timestamp: String,
maxLines: Int,
modifier: Modifier = Modifier,
) {
val annotatedString = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.White)) {
append(text)
}
pushStyle(SpanStyle(fontSize = 14.sp))
appendInlineContent("timestamp", timestamp)
pop()
}
val inlineContent = remember(text) {
mutableMapOf(
"timestamp" to InlineTextContent(
placeholder = Placeholder(
height = 20.sp,
width = 20.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
),
children = {
Text(
text = it,
color = Color.Yellow,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
)
)
}
Text(
text = annotatedString,
inlineContent = inlineContent,
maxLines = maxLines,
overflow = TextOverflow.Ellipsis,
)
}
@Preview(widthDp = 200)
@Composable
private fun PreviewTextWithTimestamp() {
Column(
modifier = Modifier
.padding(16.dp)
.border(1.dp, Color.Green)
) {
TextWithTimestamp(
text = "MedalParrot, MedalPanda, MedalCarrot and 83+ liked your clip!",
timestamp = "1h",
maxLines = 2,
)
Divider(color = Color.Green)
TextWithTimestamp(
text = "MedalParrot, MedalPanda, MedalCarrot and 83+ liked your clip!",
timestamp = "1h",
maxLines = 3,
)
}
}
Without overflow
, it works as expected, but with TextOverflow.Ellipsis
, only ...
is shown, but inline content doesn't.
In design, I have limitation of content width and defined max lines. The text can be any length:
Upvotes: 4
Views: 751
Reputation: 5513
You can implement your own truncation logic by using onTextLayout
callback.
The truncation logic in this code snippet aims to ensure that the displayed text
fits within the specified maximum number of lines. It achieves this by iteratively removing characters from the end of the text
and appending an ellipsis ("…") until the text no longer overflows. The onTextLayout
callback detects overflow and triggers a recomposition with an incremented truncationCount
, leading to a shorter version of the text
being displayed. This process repeats until the text fits within the allowed lines, at which point the final truncated version is rendered.
The shouldDraw
variable acts as a control mechanism to ensure that the text content is only rendered when it's fully truncated and fits within the specified maximum lines. It prevents intermediate, partially truncated versions from being displayed during the truncation process, resulting in a smoother user experience by only showing the final, correctly truncated text.
@Composable
fun TextWithTimestamp(
text: String,
timestamp: String,
maxLines: Int,
modifier: Modifier = Modifier,
) {
val isInPreviewMode = LocalInspectionMode.current
var shouldDraw by remember { mutableStateOf(false) }
var truncationCount by remember { mutableStateOf(0) }
val textToShow by remember(truncationCount) {
derivedStateOf {
if (truncationCount == 0) text
else "${text.dropLast(truncationCount)}…"
}
}
val annotatedString = buildAnnotatedString {
withStyle(style = SpanStyle(color = Color.White)) {
append(textToShow)
}
pushStyle(SpanStyle(fontSize = 14.sp))
appendInlineContent("timestamp", timestamp)
pop()
}
val inlineContent = remember(text) {
mutableMapOf(
"timestamp" to InlineTextContent(
placeholder = Placeholder(
height = 20.sp,
width = 20.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.Center,
),
children = {
Text(
text = it,
color = Color.Yellow,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
)
)
}
Text(
modifier = Modifier.drawWithContent { if (shouldDraw || isInPreviewMode) drawContent() },
text = annotatedString,
inlineContent = inlineContent,
maxLines = maxLines,
overflow = TextOverflow.Clip,
onTextLayout = { result ->
if (result.hasVisualOverflow) {
truncationCount++
shouldDraw = false
} else {
shouldDraw = true
}
},
)
}
@Preview(widthDp = 200)
@Composable
private fun PreviewTextWithTimestamp() {
Column(
modifier = Modifier
.background(Color.Black)
.width(200.dp)
.padding(16.dp)
.border(1.dp, Color.Green)
) {
TextWithTimestamp(
text = "MedalParrot, MedalPanda, MedalCarrot and 83+ liked your clip!",
timestamp = "1h",
maxLines = 2,
)
Divider(color = Color.Green)
TextWithTimestamp(
text = "MedalParrot, MedalPanda, MedalCarrot and 83+ liked your clip!",
timestamp = "1h",
maxLines = 3,
)
}
}
Upvotes: 0