Android Jetpack Compose:如何显示字符串资源中的样式文本
Android Jetpack Compose: How to show styled Text from string resources
我的 strings.xml
中有一个字符串,它针对不同的语言进行了本地化。
每个本地化的字符串都带有 Html 标签。
使用 Android TextView
,我能够通过读取字符串资源很好地显示样式文本。
考虑到 Jetpack Compose 目前 (1.0.0-rc02)
不支持 Html 标签,我尝试在 AndroidView
可组合项中使用 TextView
,遵循官方文档:https://developer.android.com/jetpack/compose/interop/interop-apis#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 样式显示来自字符串资源 的文本 ?
下面两个问题类似,但是没有从资源中读取字符串:
Android Compose: How to use HTML tags in a Text view
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
}
)
}
目前,您可以使用 AnnotatedString
class 在 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.stack.imgur.com/XSMMB.png
更多信息:https://developer.android.com/jetpack/compose/text?hl=nl#multiple-styles
正在讨论如何在 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))
我的 strings.xml
中有一个字符串,它针对不同的语言进行了本地化。
每个本地化的字符串都带有 Html 标签。
使用 Android TextView
,我能够通过读取字符串资源很好地显示样式文本。
考虑到 Jetpack Compose 目前 (1.0.0-rc02)
不支持 Html 标签,我尝试在 AndroidView
可组合项中使用 TextView
,遵循官方文档:https://developer.android.com/jetpack/compose/interop/interop-apis#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 样式显示来自字符串资源 的文本 ?
下面两个问题类似,但是没有从资源中读取字符串:
Android Compose: How to use HTML tags in a Text view
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
}
)
}
目前,您可以使用 AnnotatedString
class 在 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.stack.imgur.com/XSMMB.png
更多信息:https://developer.android.com/jetpack/compose/text?hl=nl#multiple-styles
正在讨论如何在 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))