如何在Android中将格式化字符串与占位符一起使用?

时间:2014-05-06 19:48:05

标签: java android string textview

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

<string name="number">My number is %1$d</string>

然后在Java代码中(在Activity的子类中):

String res = getString(R.string.number);
String formatted = String.format(res, 5);

甚至更简单:

String formatted = getString(R.string.number, 5);

也可以在Android字符串资源中使用一些HTML标记:

<string name="underline"><u>Underline</u> example</string>

由于String本身无法保存有关格式的任何信息,因此应使用getText(int)代替getString(int)方法:

CharSequence formatted = getText(R.string.underline);

然后,返回的CharSequence可以传递给Android小工具,例如TextView,标记的短语会加下划线。

但是,我无法找到如何将这两个方法组合在一起,使用格式化字符串和占位符:

<string name="underlined_number">My number is <u>%1$d</u></string>

如何处理Java代码中的上述资源以在TextView中显示它,用%1$d替换整数?

7 个答案:

答案 0 :(得分:20)

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

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));
}

此方法不需要在格式化的字符串中也不需要手动转义HTML标记,也不需要替换占位符的字符串。

答案 1 :(得分:16)

<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);

此处有更多信息:http://developer.android.com/guide/topics/resources/string-resource.html

答案 2 :(得分:2)

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

以下是Kotlin

中接受的答案
com.google.dagger:dagger-compiler:2.0

接受答案的问题是,当格式化参数本身被设置样式时(即跨越而不是字符串),它似乎不起作用。通过实验,它似乎做了奇怪的事情,可能与我们没有逃避非String CharSequences的事实有关。我看到了,如果我打电话

@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))
}

其中R.id.my_format_string是:

context.getText(R.id.my_format_string, myHelloSpanned)

和myHelloSpanned是一个看起来像&lt; b&gt; hello&lt; / b&gt; 的跨区(即它会有HTML <string name="my_format_string">===%1$s===</string> )然后我得到=== 你好< / strong> ===(即HTML <i>&lt;b&gt;hello&lt;/b&gt;</i>)。

这是错的,我应该得到=== &lt; b&gt; hello&lt; / b&gt; ===。

我尝试通过在应用===<b>hello</b>===之前将所有CharSequences转换为HTML来解决此问题,这是我的结果代码。

String.format

然而,这并不是很有效,因为当你调用@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) } } 时,它会在所有内容周围放置Html.toHtml标记,即使输入中没有额外的填充。换句话说,<p>不等于Html.fromHtml(Html.toHtml(myHelloSpanned)) - 它有额外的填充。我不知道怎么解决这个问题。

答案 3 :(得分:1)

更新:此答案https://stackoverflow.com/a/56944152/6007104已更新,现在是首选答案

这是一个更具可读性的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的扩展名-只需记住将args散布在它们之间即可:

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

答案 4 :(得分:1)

这是最终对我有用的代码

strings.xml

.grid {
  display: grid;
  grid-template-columns: 4rem auto;
  width:100vw;
}
.body {
  background:red;
  width:100%;
  height:100vh;
}
.second-nav {
  width:100%;
  background:blue;
  height:4rem;
  text-align:right;
  color:white;
}
.table {
  width:100%;
  background:green;
  height:100%;
  overflow:auto;
}
.wide-item {
  width:120rem;
  height:4rem;
  background:purple;
}

Kotlin 代码

<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>

使用它,我也可以在 Android 上渲染带有样式占位符的样式文本

输出

联系我们的团队555 555 555选择3激活。

然后我能够扩展此解决方案以创建以下 Compose 方法。

Jetpack Compose 用户界面

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)
}

答案 5 :(得分:0)

Kotlin扩展功能

  • 适用于所有API版本
  • 处理多个参数

用法示例

textView.text = context.getText(R.string.html_formatted, "Hello in bold")

包装在CDATA部分中的HTML字符串资源

<string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B>]]></string>

结果

粗体:粗体字

代码

/**
 * Create a formatted CharSequence from a string resource containing arguments and HTML formatting
 *
 * The string resource must be wrapped in a CDATA section so that the HTML formatting is conserved.
 *
 * Example of an HTML formatted string resource:
 * <string name="html_formatted"><![CDATA[ bold text: <B>%1$s</B> ]]></string>
 */
fun Context.getText(@StringRes id: Int, vararg args: Any?): CharSequence {
  val text = String.format(getString(id), *args)
  return if (android.os.Build.VERSION.SDK_INT >= 24)
    Html.fromHtml(text, Html.FROM_HTML_MODE_COMPACT)
  else
    Html.fromHtml(text)
}

答案 6 :(得分:-1)

您可以使用java.lang.String在Kotlin中进行字符串格式化

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))
}