🙆

jetpack composeで、textにoverflowを設定しつつ拡張子だけ表示する

2023/08/25に公開

用途としては、こんな感じ。

ファイル名をText composableで表示するときに、普通にtextにoverflowを設定すると最後が省略されて拡張子が見えなくなるので、なんとかしようという話。
参考

色々カスタマイズの余地はある。

@Composable
fun EllipsisTextWithExtension(
    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,
    maxLines: Int = Int.MAX_VALUE,
    onTextLayout: (TextLayoutResult) -> Unit = {},
    style: TextStyle = LocalTextStyle.current,
) {
    var textLayoutResultState by remember(text) {
        mutableStateOf<TextLayoutResult?>(null)
    }
    var extensionWidth: Float by remember { mutableFloatStateOf(0f) }
    var maxLineString: String by remember { mutableStateOf("") }
    var maxLineStringWidth: Float by remember { mutableFloatStateOf(0f) }
    var counter: Int by remember { mutableIntStateOf(0) }
    SubcomposeLayout(modifier) { constraints ->
        subcompose("measure") {
            Text(
                text = text,
                modifier = modifier,
                color = color,
                fontSize = fontSize,
                fontStyle = fontStyle,
                fontWeight = fontWeight,
                fontFamily = fontFamily,
                letterSpacing = letterSpacing,
                textDecoration = textDecoration,
                textAlign = textAlign,
                lineHeight = lineHeight,
                softWrap = softWrap,
                maxLines = Int.MAX_VALUE,
                onTextLayout = {
                    textLayoutResultState = it
                },
                style = style,
            )
        }.first().measure(Constraints())
        val textLayoutResult = textLayoutResultState
            ?: return@SubcomposeLayout layout(0, 0) {}
        val placeable = subcompose("visible") {
            val finalText = remember(text, textLayoutResult, constraints.maxWidth) {
                val lineCount = textLayoutResult.lineCount
                if (lineCount <= maxLines) {
                    return@remember text
                } else {
                    try {
                        val maxLineStartOffset = textLayoutResult.getLineStart(maxLines - 1)
                        val maxLineEndOffset = textLayoutResult.getLineEnd(maxLines - 1, true)
                        val lastLineEndOffset = textLayoutResult.getLineEnd(lineCount - 1, true)
                        val periodOffset = text.indexOf(".")
                        val extension = text.substring(periodOffset, lastLineEndOffset)
                        for (i in periodOffset until lastLineEndOffset) {
                            extensionWidth += textLayoutResult.getBoundingBox(i).width
                        }
                        val ellipsisWidth = textLayoutResult.getBoundingBox(periodOffset).width * 3
                        val availableWidth = constraints.maxWidth

                        while (availableWidth - maxLineStringWidth - ellipsisWidth - extensionWidth > 0) {
                            maxLineString = text.substring(maxLineStartOffset, maxLineStartOffset + counter)
                            maxLineStringWidth += textLayoutResult.getBoundingBox(maxLineStartOffset + counter).width
                            counter ++
                            if (maxLineStartOffset + counter > maxLineEndOffset) {
                                throw IllegalStateException("The limit of chars that can be retrieved on this line has been exceeded.")
                            }
                        }
                        text.substring(0, maxLineStartOffset) + maxLineString + "..." + extension
                    } catch (exception: Exception) {
                        exception.stackTrace
                        text
                    }
                }
            }
            Text(
                text = finalText,
                modifier = modifier,
                color = color,
                fontSize = fontSize,
                fontStyle = fontStyle,
                fontWeight = fontWeight,
                fontFamily = fontFamily,
                letterSpacing = letterSpacing,
                textDecoration = textDecoration,
                textAlign = textAlign,
                lineHeight = lineHeight,
                softWrap = softWrap,
                maxLines = maxLines,
                onTextLayout = onTextLayout,
                style = style,
            )
        }[0].measure(constraints)
        layout(placeable.width, placeable.height) {
            placeable.place(0, 0)
        }
    }
}
株式会社THIRD エンジニアブログ

Discussion