如何改进Spanned转AnnotatedString函数以支持换行符\n?
改进Spanned转AnnotatedString以支持换行符
以下是一段将Android原生Spanned转换为Jetpack ComposeAnnotatedString的代码,可保留粗体、斜体、下划线和文本颜色格式:
/** * 将[Spanned]转换为[AnnotatedString],尽可能保留原有格式。 * 目前支持`粗体`、`斜体`、`下划线`和`颜色`格式。 */ fun Spanned.toAnnotatedString(): AnnotatedString = buildAnnotatedString { val spanned = this@toAnnotatedString append(spanned.toString()) getSpans(0, spanned.length, Any::class.java).forEach { span -> val start = getSpanStart(span) val end = getSpanEnd(span) when (span) { is StyleSpan -> when (span.style) { Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), start, end) Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), start, end) Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), start, end) } is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), start, end) is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), start, end) } } }
当前代码虽能保留多种格式,但无法针对性处理文本中的\n换行符,以下是改进方案:
改进后代码
/** * 将[Spanned]转换为[AnnotatedString],保留格式并支持`\n`换行。 * 支持`粗体`、`斜体`、`下划线`、`颜色`格式,以及识别换行符实现换行。 */ fun Spanned.toAnnotatedStringWithLineBreaks(): AnnotatedString = buildAnnotatedString { val text = this@toAnnotatedString.toString() // 提前缓存所有样式及对应的文本范围 val spanRangeMap = getSpans(0, length, Any::class.java).associateWith { IntRange(getSpanStart(it), getSpanEnd(it)) } var currentTextOffset = 0 text.splitToSequence('\n').forEachIndexed { lineIndex, lineContent -> // 非首行先添加换行 if (lineIndex > 0) { appendLine() currentTextOffset += 1 // 计入换行符的长度 } // 添加当前行文本 append(lineContent) // 为当前行匹配并应用样式 spanRangeMap.forEach { (span, originalRange) -> val spanStart = originalRange.first val spanEnd = originalRange.last // 计算样式在当前AnnotatedString中的实际位置 val adjustedStart = maxOf(currentTextOffset, spanStart) - currentTextOffset + (length - lineContent.length) val adjustedEnd = minOf(currentTextOffset + lineContent.length, spanEnd) - currentTextOffset + (length - lineContent.length) if (adjustedStart < adjustedEnd) { when (span) { is StyleSpan -> when (span.style) { Typeface.BOLD -> addStyle(SpanStyle(fontWeight = FontWeight.Bold), adjustedStart, adjustedEnd) Typeface.ITALIC -> addStyle(SpanStyle(fontStyle = FontStyle.Italic), adjustedStart, adjustedEnd) Typeface.BOLD_ITALIC -> addStyle(SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic), adjustedStart, adjustedEnd) } is UnderlineSpan -> addStyle(SpanStyle(textDecoration = TextDecoration.Underline), adjustedStart, adjustedEnd) is ForegroundColorSpan -> addStyle(SpanStyle(color = Color(span.foregroundColor)), adjustedStart, adjustedEnd) } } } currentTextOffset += lineContent.length } }
改进说明
- 逐行处理文本:将原文本按
\n分割为多行,逐段添加到AnnotatedString,非首行前插入换行符。 - 样式索引校准:由于文本被分割,重新计算原Spanned中样式的对应位置,避免样式错位。
- 兼容原有格式:完整保留了原函数对粗体、斜体、下划线和颜色的处理逻辑。
使用该函数转换后的AnnotatedString,在Compose的Text组件中可正确识别\n实现换行,同时保留所有原有格式。
内容的提问来源于stack exchange,提问作者Сергей Беляков
相关产品推荐
相关产品推荐

