Java 如何在Android中使用格式化字符串和占位符?

Java 如何在Android中使用格式化字符串和占位符?,java,android,string,textview,Java,Android,String,Textview,在Android中,可以在字符串中使用占位符,例如: <string name="number">My number is %1$d</string> 或者更简单: String formatted = getString(R.string.number, 5); 也可以在Android字符串资源中使用一些HTML标记: <string name="underline"><u>Underline</u> example</st

在Android中,可以在字符串中使用占位符,例如:

<string name="number">My number is %1$d</string>
或者更简单:

String formatted = getString(R.string.number, 5);
也可以在Android字符串资源中使用一些HTML标记:

<string name="underline"><u>Underline</u> example</string>
然后,返回的
CharSequence
可以传递给Android小部件,如
TextView
,标记的短语将加下划线

但是,我找不到如何结合使用格式化字符串和占位符来组合这两种方法:

<string name="underlined_number">My number is <u>%1$d</u></string>
我的号码是%1$d
如何在Java代码中处理上述资源,以将其显示在
文本视图中,并用整数替换
%1$d
<resources>
  <string name="welcome_messages">Hello, %1$s! You have &lt;b>%2$d new messages&lt;/b>.</string>
</resources>


Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);
CharSequence styledText = Html.fromHtml(text);
您好,%1$s!您有b>%2$d封新邮件/b>。 Resources res=getResources(); String text=String.format(res.getString(R.String.welcome_messages)、用户名、邮件数); CharSequence styledText=Html.fromHtml(文本);

这里有更多信息:

最后,我找到了一个可行的解决方案,并编写了自己的方法来替换占位符,保留格式:

public static CharSequence getText(Context context, int id, Object... args) {
    for(int i = 0; i < args.length; ++i)
        args[i] = args[i] instanceof String? TextUtils.htmlEncode((String)args[i]) : args[i];
    return Html.fromHtml(String.format(Html.toHtml(new SpannedString(context.getText(id))), args));
}
publicstaticcharsequencegettext(上下文、int-id、对象…args){
对于(int i=0;i

这种方法不需要在正在格式化的字符串或替换占位符的字符串中手动转义HTML标记。

对于要替换不带数字格式的占位符(即前导零、逗号后的数字)的简单情况,可以使用方形库

用法非常简单:首先,您必须将字符串资源中的占位符更改为以下更简单的格式:

<string name="underlined_number">My number is <u> {number} </u></string>

格式化的
CharSequence
也会设置样式。如果您需要格式化数字,您可以始终使用
String.format(“%03d”,5)
预先格式化数字,然后在
.put()
函数中使用生成的字符串。

与接受的答案类似,我尝试为此编写一个Kotlin扩展方法

这是Kotlin的公认答案

@Suppress("DEPRECATION")
fun Context.getText(id: Int, vararg args: Any): CharSequence {
    val escapedArgs = args.map {
        if (it is String) TextUtils.htmlEncode(it) else it
    }.toTypedArray()
    return Html.fromHtml(String.format(Html.toHtml(SpannedString(getText(id))), *escapedArgs))
}
接受答案的问题在于,当格式参数本身被设置样式时(即,跨距,而不是字符串),它似乎不起作用。通过实验,它似乎做了一些奇怪的事情,可能是因为我们没有逃逸非字符串字符序列。如果我打电话给你,我会看到的

context.getText(R.id.my_format_string, myHelloSpanned)
其中R.id.my_格式_字符串为:

<string name="my_format_string">===%1$s===</string>
然而,这并不是很有效,因为当您调用
Html.toHtml
时,它会在所有内容周围放置
标记,即使输入中没有额外的填充。换句话说,
Html.fromHtml(Html.toHtml(myhellospaned))
不等于
myhellospaned
——它有额外的填充。我不知道如何很好地解决这个问题。

Kotlin扩展函数
  • 适用于所有API版本
  • 处理多个参数
示例用法 包装在CDATA节中的HTML字符串资源
%1$s]>
结果 粗体文本:您好,粗体

代码
/**
*从包含参数和HTML格式的字符串资源创建格式化的CharSequence
*
*字符串资源必须包装在CDATA节中,以便保留HTML格式。
*
*HTML格式的字符串资源示例:
*%1$s]]>
*/
有趣的Context.getText(@StringRes id:Int,vararg args:Any?):CharSequence=
HTML(String.format(getString(id),*args),HtmlCompat.FROM\u HTML\u MODE\u COMPACT)
您可以在Kotlin中使用java.lang.String进行字符串格式设置
fun main(args:Array){
变量值1=1
var value2=“2”
var值3=3.0
println(java.lang.String.format(“%d,%s,%6f”,value1,value2,value3))
}

更新:此答案已更新,现在是首选答案

这里有一个更具可读性的Kotlin扩展,它不使用不推荐的API,适用于所有Android版本,并且不需要在CDATA部分包装字符串:

fun Context.getText(id: Int, vararg args: Any): CharSequence {

    val escapedArgs = args.map {
        if (it is String) TextUtils.htmlEncode(it) else it
    }.toTypedArray()

    val resource = SpannedString(getText(id))
    val htmlResource = HtmlCompat.toHtml(resource, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
    val formattedHtml = String.format(htmlResource, *escapedArgs)
    return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
您可以添加别名作为Fragment的扩展-只需记住将参数分散在以下两个字段之间:

fun Fragment.getText(id: Int, vararg args: Any) = requireContext().getText(id, *args)

这就是最终对我有效的代码

strings.xml
请在%1$s上与我们的团队联系以激活。
555555555选择3
科特林代码
fun span.toHtmlWithoutParagraphs():字符串{
返回HtmlCompat.toHtml(这是HtmlCompat.TO\u HTML\u段落\u行\u连续)
.substringAfter(“

”)。substringBeforeLast(

”) } 有趣的资源。getText(@StringRes id:Int,vararg args:Any):CharSequence{ val escapedArgs=args.map{ 如果(它是跨距的)它。toHtmlWithoutParagraphs()否则它 }.toTypedArray() val资源=跨距字符串(getText(id)) val htmlResource=resource.toHtmlWithoutParagraphs() val formattedHtml=String.format(htmlResource,*escapedArgs) 返回HtmlCompat.fromHtml(格式化HTML,HtmlCompat.FROM\uHTML\uMode\uLegacy) }
使用这个,我也可以在Android上用样式占位符呈现样式文本

输出 请致电555 555 555 Opt 3与我们的团队联系以激活

然后,我可以扩展这个解决方案,创建以下Compose方法

Jetpack组合用户界面
@Composable
趣味annotatedStringResource(@StringRes id:Int,vararg formatArgs:Any):AnnotatedString{
val resources=LocalContext.current.resources
返回(id){
val text=resources.getText(id,*formatArgs)
Spannablestring到AnnotatedString(文本)
}
}
@组合的
趣味annotatedStringResource(@StringRes id:Int):AnnotatedString{
val resources=LocalContext.current.resources
返回(id){
val text=resources.getText(id)
Spannablestring到AnnotatedString(文本)
}
}
private fun Spannablestring到AnnotatedString(文本:CharSequence):AnnotatedString{
返回if(文本为跨距){
瓦尔
<string name="my_format_string">===%1$s===</string>
@Suppress("DEPRECATION")
fun Context.getText(@StringRes resId: Int, vararg formatArgs: Any): CharSequence {
    // First, convert any styled Spanned back to HTML strings before applying String.format. This
    // converts the styling to HTML and also does HTML escaping.
    // For other CharSequences, just do HTML escaping.
    // (Leave any other args alone.)
    val htmlFormatArgs = formatArgs.map {
        if (it is Spanned) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                Html.toHtml(it, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
            } else {
                Html.toHtml(it)
            }
        } else if (it is CharSequence) {
            Html.escapeHtml(it)
        } else {
            it
        }
    }.toTypedArray()

    // Next, get the format string, and do the same to that.
    val formatString = getText(resId);
    val htmlFormatString = if (formatString is Spanned) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Html.toHtml(formatString, Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
        } else {
            Html.toHtml(formatString)
        }
    } else {
        Html.escapeHtml(formatString)
    }

    // Now apply the String.format
    val htmlResultString = String.format(htmlFormatString, *htmlFormatArgs)

    // Convert back to a CharSequence, recovering any of the HTML styling.
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        Html.fromHtml(htmlResultString, Html.FROM_HTML_MODE_LEGACY)
    } else {
        Html.fromHtml(htmlResultString)
    }
}
textView.text = context.getText(R.string.html_formatted, "Hello in bold")
<string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B>]]></string>
fun main(args : Array<String>) {
  var value1 = 1
  var value2 = "2"
  var value3 = 3.0
  println(java.lang.String.format("%d, %s, %6f", value1, value2, value3))
}
fun Context.getText(id: Int, vararg args: Any): CharSequence {

    val escapedArgs = args.map {
        if (it is String) TextUtils.htmlEncode(it) else it
    }.toTypedArray()

    val resource = SpannedString(getText(id))
    val htmlResource = HtmlCompat.toHtml(resource, HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE)
    val formattedHtml = String.format(htmlResource, *escapedArgs)
    return HtmlCompat.fromHtml(formattedHtml, HtmlCompat.FROM_HTML_MODE_LEGACY)
}
fun Fragment.getText(id: Int, vararg args: Any) = requireContext().getText(id, *args)
<string name="launch_awaiting_instructions">Contact <b>our</b> team on %1$s to activate.</string>
<string name="support_contact_phone_number"><b>555 555 555</b> Opt <b>3</b></string>
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 = LocalContext.current.resources
    return remember(id) {
        val text = resources.getText(id, *formatArgs)
        spannableStringToAnnotatedString(text)
    }
}

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

private fun spannableStringToAnnotatedString(text: CharSequence): AnnotatedString {
    return if (text is Spanned) {
        val spanStyles = mutableListOf<AnnotatedString.Range<SpanStyle>>()
        spanStyles.addAll(text.getSpans(0, text.length, UnderlineSpan::class.java).map {
            AnnotatedString.Range(
                SpanStyle(textDecoration = TextDecoration.Underline),
                text.getSpanStart(it),
                text.getSpanEnd(it)
            )
        })
        spanStyles.addAll(text.getSpans(0, text.length, StyleSpan::class.java).map {
            AnnotatedString.Range(
                SpanStyle(fontWeight = FontWeight.Bold),
                text.getSpanStart(it),
                text.getSpanEnd(it)
            )
        })
        AnnotatedString(text.toString(), spanStyles = spanStyles)
    } else {
        AnnotatedString(text.toString())
    }
}