Skip to content

Instantly share code, notes, and snippets.

@PhilipDukhov
Created September 7, 2021 10:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PhilipDukhov/3d38f1a1fe11cfdec846a916b520b1c0 to your computer and use it in GitHub Desktop.
Save PhilipDukhov/3d38f1a1fe11cfdec846a916b520b1c0 to your computer and use it in GitHub Desktop.
Middle ellipsis in Text for Jetpack Compose
@Composable
fun MiddleEllipsisText(
text: String,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
fontSize: TextUnit = TextUnit.Unspecified,
fontStyle: FontStyle? = null,
fontWeight: FontWeight? = null,
fontFamily: FontFamily? = null,
letterSpacing: TextUnit = TextUnit.Unspecified,
textDecoration: TextDecoration? = null,
textAlign: TextAlign? = null,
lineHeight: TextUnit = TextUnit.Unspecified,
softWrap: Boolean = true,
onTextLayout: (TextLayoutResult) -> Unit = {},
style: TextStyle = LocalTextStyle.current
) {
Box(modifier) {
var textLayoutResult by remember {
mutableStateOf<TextLayoutResult?>(null)
}
val layoutText = remember(text) { text + ellipsisText }
val finalText = remember(textLayoutResult) {
textLayoutResult?.let { textLayoutResult ->
if (textLayoutResult.getBoundingBox(text.count()).top == 0f) {
// text not including ellipsis fits on the first line.
return@let text
}
val ellipsisWidth = layoutText.indices.toList()
.takeLast(ellipsisCharactersCount)
.let widthLet@{ indices ->
// fix this bug: https://issuetracker.google.com/issues/197146630
// in this case width is invalid
for (i in indices) {
val width = textLayoutResult.getBoundingBox(i).width
if (width > 0) {
return@widthLet width * ellipsisCharactersCount
}
}
// this should not happen, because
// this error occurs only for the last character in the string
throw IllegalStateException("all ellipsis chars have invalid width")
}
val availableWidth = textLayoutResult.size.width - ellipsisWidth
val startCounter = BoundCounter(text, textLayoutResult) { it }
val endCounter = BoundCounter(text, textLayoutResult) { text.indices.last - it }
while (availableWidth - startCounter.width - endCounter.width > 0) {
val possibleEndWidth = endCounter.widthWithNextChar()
if (
startCounter.width >= possibleEndWidth
&& availableWidth - startCounter.width - possibleEndWidth >= 0
) {
endCounter.addNextChar()
} else if (availableWidth - startCounter.widthWithNextChar() - endCounter.width >= 0) {
startCounter.addNextChar()
} else {
break
}
}
startCounter.string.trimEnd() + ellipsisText + endCounter.string.reversed()
.trimStart()
}
}
Text(
text = text + ellipsisText,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
softWrap = softWrap,
maxLines = 1,
onTextLayout = {
textLayoutResult = it
},
style = style,
modifier = Modifier
.alpha(0f)
)
if (finalText != null) {
Text(
text = finalText,
color = color,
fontSize = fontSize,
fontStyle = fontStyle,
fontWeight = fontWeight,
fontFamily = fontFamily,
letterSpacing = letterSpacing,
textDecoration = textDecoration,
textAlign = textAlign,
lineHeight = lineHeight,
softWrap = softWrap,
onTextLayout = onTextLayout,
overflow = TextOverflow.Visible,
style = style,
modifier = Modifier
)
}
}
}
private const val ellipsisCharactersCount = 3
private const val ellipsisCharacter = '.'
private val ellipsisText =
List(ellipsisCharactersCount) { ellipsisCharacter }.joinToString(separator = "")
private class BoundCounter(
private val text: String,
private val textLayoutResult: TextLayoutResult,
private val charPosition: (Int) -> Int,
) {
var string = ""
private set
var width = 0f
private set
private var _nextCharWidth: Float? = null
private var invalidCharsCount = 0
fun widthWithNextChar(): Float =
width + nextCharWidth()
private fun nextCharWidth(): Float =
_nextCharWidth ?: run {
var boundingBox: Rect
// invalidCharsCount fixes this bug: https://issuetracker.google.com/issues/197146630
invalidCharsCount--
do {
boundingBox = textLayoutResult
.getBoundingBox(charPosition(string.count() + ++invalidCharsCount))
} while (boundingBox.right == 0f)
_nextCharWidth = boundingBox.width
boundingBox.width
}
fun addNextChar() {
string += text[charPosition(string.count())]
width += nextCharWidth()
_nextCharWidth = null
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment