如何解决Android Compose:如何在文本视图中使用 HTML 标签
我有来自外部源的字符串,其中包含以下格式的 HTML 标记: “你好,我是粗体文字”
在撰写之前,我会在 HTML 字符串的开头使用 CDATA,使用 Html.fromHtml() 转换为 Spanned 并将其传递给 TextView。 TextView 会将粗体字加粗。
我曾尝试使用 Compose 复制此方法,但找不到让我成功实现它的确切步骤。
感谢收到任何建议。
解决方法
Compose Text() 尚不支持 HTML。它只是刚刚进入测试版,所以它可能会到达。
我们目前实施的解决方案(这并不完美)是依靠旧的 TextView 控件,Compose 允许您这样做。
https://developer.android.com/jetpack/compose/interop#views-in-compose
,目前还没有官方的 Composable 可以做到这一点。现在我正在使用一个带有 TextView 的 AndroidView。不是最好的解决方案,但它很简单,可以解决问题。
@Composable
fun HtmlText(html: String,modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,factory = { context -> TextView(context) },update = { it.text = HtmlCompat.fromHtml(html,HtmlCompat.FROM_HTML_MODE_COMPACT) }
)
}
,
由于我将 Kotlin Multiplatform 项目与 Android Jetpack Compose 和 JetBrains Compose for Desktop 结合使用,因此我真的无法选择只使用 Android 的 TextView。
因此,我从 turbohenoch's answer 中汲取了灵感,并尽力将其扩展为能够解释多个(可能是嵌套的)HTML 格式标记。
代码肯定可以改进,而且它对 HTML 错误一点也不健壮,但我确实用包含 <u>
和 <b>
标签的文本对其进行了测试,至少它可以正常工作。
代码如下:
/**
* The tags to interpret. Add tags here and in [tagToStyle].
*/
private val tags = linkedMapOf(
"<b>" to "</b>","<i>" to "</i>","<u>" to "</u>"
)
/**
* The main entry point. Call this on a String and use the result in a Text.
*/
fun String.parseHtml(): AnnotatedString {
val newlineReplace = this.replace("<br>","\n")
return buildAnnotatedString {
recurse(newlineReplace,this)
}
}
/**
* Recurses through the given HTML String to convert it to an AnnotatedString.
*
* @param string the String to examine.
* @param to the AnnotatedString to append to.
*/
private fun recurse(string: String,to: AnnotatedString.Builder) {
//Find the opening tag that the given String starts with,if any.
val startTag = tags.keys.find { string.startsWith(it) }
//Find the closing tag that the given String starts with,if any.
val endTag = tags.values.find { string.startsWith(it) }
when {
//If the String starts with a closing tag,then pop the latest-applied
//SpanStyle and continue recursing.
tags.any { string.startsWith(it.value) } -> {
to.pop()
recurse(string.removeRange(0,endTag!!.length),to)
}
//If the String starts with an opening tag,apply the appropriate
//SpanStyle and continue recursing.
tags.any { string.startsWith(it.key) } -> {
to.pushStyle(tagToStyle(startTag!!))
recurse(string.removeRange(0,startTag.length),to)
}
//If the String doesn't start with an opening or closing tag,but does contain either,//find the lowest index (that isn't -1/not found) for either an opening or closing tag.
//Append the text normally up until that lowest index,and then recurse starting from that index.
tags.any { string.contains(it.key) || string.contains(it.value) } -> {
val firstStart = tags.keys.map { string.indexOf(it) }.filterNot { it == -1 }.minOrNull() ?: -1
val firstEnd = tags.values.map { string.indexOf(it) }.filterNot { it == -1 }.minOrNull() ?: -1
val first = when {
firstStart == -1 -> firstEnd
firstEnd == -1 -> firstStart
else -> min(firstStart,firstEnd)
}
to.append(string.substring(0,first))
recurse(string.removeRange(0,first),to)
}
//There weren't any supported tags found in the text. Just append it all normally.
else -> {
to.append(string)
}
}
}
/**
* Get a [SpanStyle] for a given (opening) tag.
* Add your own tag styling here by adding its opening tag to
* the when clause and then instantiating the appropriate [SpanStyle].
*
* @return a [SpanStyle] for the given tag.
*/
private fun tagToStyle(tag: String): SpanStyle {
return when (tag) {
"<b>" -> {
SpanStyle(fontWeight = FontWeight.Bold)
}
"<i>" -> {
SpanStyle(fontStyle = FontStyle.Italic)
}
"<u>" -> {
SpanStyle(textDecoration = TextDecoration.Underline)
}
//This should only throw if you add a tag to the [tags] Map and forget to add it
//to this function.
else -> throw IllegalArgumentException("Tag $tag is not valid.")
}
}
我已尽力做出明确的评论,但这里有一个简短的解释。 tags
变量是要跟踪的标签的映射,键是开始标签,值是它们对应的结束标签。此处的任何内容也需要在 tagToStyle()
函数中处理,以便代码可以为每个标签获得合适的 SpanStyle。
然后递归扫描输入字符串,寻找跟踪的开始和结束标签。
如果给定的 String 以结束标记开头,它将弹出最近应用的 SpanStyle(从那时起将其从附加的文本中删除)并在删除该标记的 String 上调用递归函数。
如果给定的 String 以开始标签开头,它将推送相应的 SpanStyle(使用 tagToStyle()
),然后在移除该标签的 String 上调用递归函数。
如果给定的字符串不以结束标签或开始标签开头,但确实包含至少其中之一,它将找到任何跟踪标签的第一次出现(开始或关闭),通常将给定字符串中的所有文本追加到该索引为止,然后在字符串上调用递归函数,从它找到的第一个跟踪标签的索引开始。
如果给定的字符串没有任何标签,它只会正常追加,不会添加或删除任何样式。
由于我在一个正在积极开发的应用程序中使用它,我可能会根据需要继续更新它。假设没有任何重大变化,最新版本应该可以在其 GitHub repository 上获得。
,对于简单的用例,您可以执行以下操作:
private fun String.parseBold(): AnnotatedString {
val parts = this.split("<b>","</b>")
return buildAnnotatedString {
var bold = false
for (part in parts) {
if (bold) {
withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
append(part)
}
} else {
append(part)
}
bold = !bold
}
}
}
并在@Composable 中使用这个 AnnotatedString
Text(text = "Hello,I am <b> bold</b> text".parseBold())
当然,当您尝试支持更多标签时,这会变得更加棘手。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。