Android Jetpack Compose:如何显示字符串资源中的样式文本

问题描述 投票:0回答:5

我的

strings.xml
中有一个字符串,它针对不同的语言进行了本地化。 每个本地化字符串都使用 Html 标签进行样式设置。

使用 Android

TextView
,我能够通过读取字符串资源来很好地显示样式文本。

考虑到 Jetpack Compose 目前

(1.0.0-rc02)
不支持 Html 标签,我尝试按照官方文档在
TextView
可组合项中使用
AndroidView
https://developer.android.com/jetpack/compose/interop/interop- api#views-in-compose

我尝试过的示例:

@Composable
fun StyledText(text: String, modifier: Modifier = Modifier) {
    AndroidView(
            modifier = modifier,
            factory = { context -> TextView(context) },
            update = {
                it.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)
            }
    )
}

strings.xml
文件中的文本:

<string name="styled_text">Sample text with <b>bold styling</b> to test</string>

但是,使用

stringResource(id = R.string.styled_text)
提供的文本没有 Html 标签。

有没有办法在 Jetpack Compose 中使用 Html 样式显示文本 来自字符串资源


以下两个问题类似,但它们从资源中读取字符串:

Jetpack 撰写在文本中显示 html

Android Compose:如何在文本视图中使用 HTML 标签

android kotlin android-jetpack-compose android-jetpack-compose-text
5个回答
16
投票

关于在 Jetpack Compose UI 上实现的讨论正在进行中: https://issuetracker.google.com/issues/139320238

经过一番研究,我提出了以下解决方案,我也在同一讨论中发布了该解决方案:

@Composable
@ReadOnlyComposable
private fun resources(): Resources {
    LocalConfiguration.current
    return LocalContext.current.resources
}

fun Spanned.toHtmlWithoutParagraphs(): String {
    return HtmlCompat.toHtml(this, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
        .substringAfter("<p dir=\"ltr\">").substringBeforeLast("</p>")
}

fun Resources.getText(@StringRes id: Int, vararg args: Any): CharSequence {
    val escapedArgs = args.map {
        if (it is Spanned) it.toHtmlWithoutParagraphs() else it
    }.toTypedArray()
    val resource = SpannedString(getText(id))
    val htmlResource = resource.toHtmlWithoutParagraphs()
    val formattedHtml = String.format(htmlResource, *escapedArgs)
    return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}

@Composable
fun annotatedStringResource(@StringRes id: Int, vararg formatArgs: Any): AnnotatedString {
    val resources = resources()
    val density = LocalDensity.current
    return remember(id, formatArgs) {
        val text = resources.getText(id, *formatArgs)
        spannableStringToAnnotatedString(text, density)
    }
}

@Composable
fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
    val resources = resources()
    val density = LocalDensity.current
    return remember(id) {
        val text = resources.getText(id)
        spannableStringToAnnotatedString(text, density)
    }
}

private fun spannableStringToAnnotatedString(
    text: CharSequence,
    density: Density
): AnnotatedString {
    return if (text is Spanned) {
        with(density) {
            buildAnnotatedString {
                append((text.toString()))
                text.getSpans(0, text.length, Any::class.java).forEach {
                    val start = text.getSpanStart(it)
                    val end = text.getSpanEnd(it)
                    when (it) {
                        is StyleSpan -> when (it.style) {
                            Typeface.NORMAL -> addStyle(
                                SpanStyle(
                                    fontWeight = FontWeight.Normal,
                                    fontStyle = FontStyle.Normal
                                ),
                                start,
                                end
                            )
                            Typeface.BOLD -> addStyle(
                                SpanStyle(
                                    fontWeight = FontWeight.Bold,
                                    fontStyle = FontStyle.Normal
                                ),
                                start,
                                end
                            )
                            Typeface.ITALIC -> addStyle(
                                SpanStyle(
                                    fontWeight = FontWeight.Normal,
                                    fontStyle = FontStyle.Italic
                                ),
                                start,
                                end
                            )
                            Typeface.BOLD_ITALIC -> addStyle(
                                SpanStyle(
                                    fontWeight = FontWeight.Bold,
                                    fontStyle = FontStyle.Italic
                                ),
                                start,
                                end
                            )
                        }
                        is TypefaceSpan -> addStyle(
                            SpanStyle(
                                fontFamily = when (it.family) {
                                    FontFamily.SansSerif.name -> FontFamily.SansSerif
                                    FontFamily.Serif.name -> FontFamily.Serif
                                    FontFamily.Monospace.name -> FontFamily.Monospace
                                    FontFamily.Cursive.name -> FontFamily.Cursive
                                    else -> FontFamily.Default
                                }
                            ),
                            start,
                            end
                        )
                        is BulletSpan -> {
                            Log.d("StringResources", "BulletSpan not supported yet")
                            addStyle(SpanStyle(), start, end)
                        }
                        is AbsoluteSizeSpan -> addStyle(
                            SpanStyle(fontSize = if (it.dip) it.size.dp.toSp() else it.size.toSp()),
                            start,
                            end
                        )
                        is RelativeSizeSpan -> addStyle(
                            SpanStyle(fontSize = it.sizeChange.em),
                            start,
                            end
                        )
                        is StrikethroughSpan -> addStyle(
                            SpanStyle(textDecoration = TextDecoration.LineThrough),
                            start,
                            end
                        )
                        is UnderlineSpan -> addStyle(
                            SpanStyle(textDecoration = TextDecoration.Underline),
                            start,
                            end
                        )
                        is SuperscriptSpan -> addStyle(
                            SpanStyle(baselineShift = BaselineShift.Superscript),
                            start,
                            end
                        )
                        is SubscriptSpan -> addStyle(
                            SpanStyle(baselineShift = BaselineShift.Subscript),
                            start,
                            end
                        )
                        is ForegroundColorSpan -> addStyle(
                            SpanStyle(color = Color(it.foregroundColor)),
                            start,
                            end
                        )
                        else -> addStyle(SpanStyle(), start, end)
                    }
                }
            }
        }
    } else {
        AnnotatedString(text.toString())
    }
}

来源:https://issuetracker.google.com/issues/139320238#comment11

使用这些辅助方法,您可以简单地调用:

Text(annotatedStringResource(R.string.your_string_resource))

14
投票

stringResource
在底层使用
resources.getString
,它会丢弃任何样式信息。您需要创建类似
textResource
的内容才能获取原始值:

@Composable
@ReadOnlyComposable
fun textResource(@StringRes id: Int): CharSequence =
    LocalContext.current.resources.getText(id)

并像这样使用它:

StyledText(textResource(id = R.string.foo))

@Composable
fun StyledText(text: CharSequence, modifier: Modifier = Modifier) {
    AndroidView(
        modifier = modifier,
        factory = { context -> TextView(context) },
        update = {
            it.text = text
        }
    )
}

4
投票

要继续使用

stringResources()
,您可以按照以下步骤操作。

第一 - 使用

<![CDATA[ … ]]>

<![CDATA[ … ]]>
之间包含所有资源 HTML 字符串,那么在调用
stringResources()
时就不会丢失 HTML 定义:

<string name="styled_text"><![CDATA[Sample text with <b>bold styling</b> to test]]></string>

第二 - 创建一个 “从

Spanned
AnnotatedString
处理器”

首先感谢这个很棒的答案

那么重要的是要知道

text
参数 (来自
Text()
可组合项)
也接受
AnnotatedString
对象,而不仅仅是
String

继续前进……我们可以使用 Jetpack

Spanned
 创建一个 
“从 AnnotatedString
buildAnnotatedString()
处理器”
算法。

在这种情况下,我会选择 “将算法创建为客户端可组合文件内的扩展

private
函数” 路径:

private 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 ForegroundColorSpan -> addStyle( // For <span style=color:blue> tag.
                    SpanStyle(color = Color.Blue),
                    start,
                    end
                )
            }
        }
    }

第三 - 致电

HtmlCompat.fromHtml()

作为将

AnnotatedString
发送到
Text()
可组合项之前的最后一步,我们需要在
String
方法上调用目标
HtmlCompat.fromHtml()
以及新的
toAnnotatedString()
扩展函数:

val textAsAnnotatedString = HtmlCompat.fromHtml(
    stringResource(id = R.string.styled_text),
    HtmlCompat.FROM_HTML_MODE_COMPACT
).toAnnotatedString()

4th - 在

Text()

上显示

然后将其显示在您的目标上

Text()
可组合:

Text(text = textAsAnnotatedString)

注意:您可以在toAnnotatedString()内部添加很多

“样式解释器”

下方打印 (红色矩形内的所有内容) 来自我的 Android 项目上的可组合

SnackBar
,使用与上述策略相同的策略。

enter image description here


0
投票

正如其他人所说,

stringResource(id = R.string.styled_text)
将删除 HTML 标签。为了防止这种情况,您可以使用
&lt;
表示“小于”符号(<) and
&gt;
表示“大于”符号 (>))转义尖括号符号。您的字符串资源将是:

<string name="styled_text">Sample text with &lt;b&gt;bold styling&lt;/b&gt; to test</string>

-1
投票

目前,您可以使用

AnnotatedString
类来在
Text
组件中显示样式文本。我将向您展示一个例子:

Text(
    text = buildAnnotatedString {
        withStyle(style = SpanStyle(fontWeight = FontWeight.Thin)) {
            append(stringResource(id = R.string.thin_string))
        }
        withStyle(style = SpanStyle(fontWeight = FontWeight.Bold)) {
            append(stringResource(id = R.string.bold_string))
        }
    },
)

根据您指定的样式,您将获得具有红色突出显示样式的文本

https://i.sstatic.net/XSMMB.png

了解更多信息:https://developer.android.com/jetpack/compose/text?hl=nl#multiple-styles

© www.soinside.com 2019 - 2024. All rights reserved.