字符串资源为您的应用提供文本字符串,并可选择应用文本样式和格式。有三种类型的资源可以为您的应用提供字符串
所有字符串都可以应用一些样式标记和格式参数。有关样式和格式字符串的信息,请参阅关于 格式和样式 的部分。
字符串
可以从应用或其他资源文件(如 XML 布局)中引用的单个字符串。
注意:字符串是一种简单的资源,使用 name
属性中提供的值(而不是 XML 文件的名称)进行引用。因此,您可以将字符串资源与一个 XML 文件中(在一个 <resources>
元素下)的其他简单资源组合在一起。
- 文件位置
res/values/filename.xml
文件名是任意的。<string>
元素的name
用作资源 ID。- 编译后的资源数据类型
- 指向
String
的资源指针。 - 资源引用
- 在 Java 中:
R.string.string_name
在 XML 中:@string/string_name
- 语法
-
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="string_name" >text_string</string> </resources>
- 元素
- 示例
- 保存在
res/values/strings.xml
的 XML 文件<?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello!</string> </resources>
此布局 XML 将字符串应用于视图
<TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" />
此应用代码检索字符串
您可以使用
getString(int)
或getText(int)
来检索字符串。getText(int)
保留应用于字符串的任何富文本样式。
字符串数组
应用程序可以引用的字符串数组。
注意: 字符串数组是一种简单的资源,它使用 name
属性中提供的 value(而不是 XML 文件的名称)来引用。因此,您可以将字符串数组资源与同一个 XML 文件中的其他简单资源组合在一起,并位于同一个 <resources>
元素下。
- 文件位置
res/values/filename.xml
文件名是任意的。<string-array>
元素的name
用作资源 ID。- 编译后的资源数据类型
- 指向
String
数组的资源指针。 - 资源引用
- 在 Java 中:
R.array.string_array_name
在 XML 中:@[package:]array/string_array_name
- 语法
-
<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="string_array_name"> <item >text_string</item> </string-array> </resources>
- 元素
- 示例
- 保存在
res/values/strings.xml
的 XML 文件<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="planets_array"> <item>Mercury</item> <item>Venus</item> <item>Earth</item> <item>Mars</item> </string-array> </resources>
此应用程序代码检索字符串数组
Kotlin
val array: Array<String> =
resources
.getStringArray
(R.array.planets_array)Java
Resources res =
getResources()
; String[] planets = res.getStringArray
(R.array.planets_array);
数量字符串(复数)
不同的语言对数量的语法一致性有不同的规则。例如,在英语中,数量 1 是一个特殊情况。我们写“1 本书”,但对于任何其他数量,我们都会写“n 本书”。这种单数和复数之间的区别非常普遍,但其他语言会做出更细致的区分。Android 支持的完整集合是 zero
、one
、two
、few
、many
和 other
。
确定对给定语言和数量使用哪个情况的规则可能非常复杂,因此 Android 为您提供了诸如 getQuantityString()
之类的方法来为您选择适当的资源。
虽然在历史上被称为“数量字符串”(在 API 中仍然这样称呼),但数量字符串仅应用于复数。例如,使用数量字符串来实现 Gmail 的“收件箱”与“收件箱(12)”(当有未读消息时)将是一个错误。使用数量字符串而不是 if
语句似乎很方便,但重要的是要注意,某些语言(如中文)根本不进行这些语法区分,因此您将始终获得 other
字符串。
使用哪个字符串的选择完全基于语法必要性。在英语中,即使数量为 0,zero
的字符串也会被忽略,因为 0 在语法上与 2 或任何其他数字(除了 1)没有区别(“零本书”、“一本”、“两本书”等等)。相反,在韩语中,只有 other
字符串被使用。
不要被诸如 two
之类的词语所误导,这些词语听起来似乎只适用于数量 2:一种语言可能要求 2、12、102(等等)都像彼此一样对待,但与其他数量不同。依靠您的翻译人员来了解他们的语言实际上坚持什么区别。
如果您的消息不包含数量数字,那么它可能不是复数的良好候选者。例如,在立陶宛语中,单数形式用于 1 和 101,因此“1 本书”被翻译为“1 knyga”,而“101 本书”被翻译为“101 knyga”。同时,“一本”是“knyga”,而“许多书”是“daug knygų”。如果英语复数消息包含“一本”(单数)和“许多书”(复数)而没有实际数字,它可以翻译为“knyga”(一本)/“daug knygų”(许多书),但使用立陶宛语规则,当数字恰好是 101 时,它将显示“knyga”(一本)。
通常可以通过使用诸如“书籍:1”之类的数量中性表达来避免数量字符串。如果您的应用程序的风格允许这样做,这将使您和您的翻译人员的生活更轻松。
在 API 24+ 上,您可以使用功能更强大的 ICU MessageFormat
类。
注意: 复数集合是一种简单的资源,它使用 name
属性中提供的 value(而不是 XML 文件的名称)来引用。因此,您可以将复数资源与同一个 XML 文件中的其他简单资源组合在一起,并位于同一个 <resources>
元素下。
- 文件位置
res/values/filename.xml
文件名是任意的。<plurals>
元素的name
用作资源 ID。- 资源引用
- 在 Java 中:
R.plurals.plural_name
- 语法
-
<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="plural_name"> <item quantity=["zero" | "one" | "two" | "few" | "many" | "other"] >text_string</item> </plurals> </resources>
- 元素
- 示例
- 保存在
res/values/strings.xml
的 XML 文件<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="numberOfSongsAvailable"> <!-- As a developer, you should always supply "one" and "other" strings. Your translators will know which strings are actually needed for their language. Always include %d in "one" because translators will need to use %d for languages where "one" doesn't mean 1 (as explained above). --> <item quantity="one">%d song found.</item> <item quantity="other">%d songs found.</item> </plurals> </resources>
XML 文件保存在
res/values-pl/strings.xml
中<?xml version="1.0" encoding="utf-8"?> <resources> <plurals name="numberOfSongsAvailable"> <item quantity="one">Znaleziono %d piosenkę.</item> <item quantity="few">Znaleziono %d piosenki.</item> <item quantity="other">Znaleziono %d piosenek.</item> </plurals> </resources>
Usage
Kotlin
val count = getNumberOfSongsAvailable() val songsFound = resources.
getQuantityString
(R.plurals.numberOfSongsAvailable, count, count)Java
int count = getNumberOfSongsAvailable(); Resources res =
getResources()
; String songsFound = res.getQuantityString
(R.plurals.numberOfSongsAvailable, count, count);当使用
getQuantityString()
方法时,如果您在字符串中包含 字符串格式化,则需要将count
传递两次。例如,对于字符串%d 首歌曲找到
,第一个count
参数选择适当的复数字符串,第二个count
参数将插入%d
占位符中。如果您的复数字符串不包含字符串格式化,则无需将第三个参数传递给getQuantityString
。
格式和样式
以下是一些有关如何正确设置字符串资源格式和样式的重要事项。
处理特殊字符
当字符串包含在 XML 中具有特殊用途的字符时,您必须根据标准 XML/HTML 转义规则转义这些字符。如果您需要转义在 Android 中具有特殊含义的字符,则应使用前面的反斜杠。
默认情况下,Android 会将空格字符序列折叠成一个空格。您可以通过将字符串的相关部分括在双引号中来避免这种情况。在这种情况下,所有空格字符(包括换行符)都将在引号区域内保留。双引号将允许您使用常规的单个未转义引号。
Character | Escaped form(s) |
---|---|
@ | \@ |
? | \? |
New line | \n |
Tab | \t |
U+XXXX Unicode character | \uXXXX |
单引号 (' ) |
以下任一
|
双引号 (" ) |
\"
请注意,用单引号将字符串括起来不起作用。 |
空格折叠和 Android 转义发生在您的资源文件被解析为 XML 之后。这意味着 <string>      </string>
(空格、标点符号空格、Unicode Em 空格)都会折叠成一个空格 (" "
),因为它们在文件被解析为 XML 之后都是 Unicode 空格。为了保留这些空格,您可以对它们进行引用 (<string>"      "</string>
) 或使用 Android 转义 (<string> \u0032 \u8200 \u8195</string>
)。
注意: 从 XML 解析器的角度来看,<string>"Test this"</string>
和 <string>"Test this"</string>
之间没有区别。两种形式都不会显示任何引号,但会触发 Android 空格保留引用(在这种情况下没有实际效果)。
格式化字符串
如果您需要格式化字符串,则可以通过将格式参数放在字符串资源中来实现,如下面的示例资源所示。
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
在此示例中,格式字符串有两个参数:%1$s
是一个字符串,%2$d
是一个十进制数。然后,通过调用 getString(int, Object...)
来格式化字符串。例如
Kotlin
var text = getString(R.string.welcome_messages, username, mailCount)
Java
String text = getString(R.string.welcome_messages, username, mailCount);
使用 HTML 标记设置样式
您可以使用 HTML 标记为字符串添加样式。例如
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="welcome">Welcome to <b>Android</b>!</string> </resources>
支持以下 HTML 元素
- 粗体:<b>
- 斜体:<i>、<cite>、<dfn>、<em>
- 25% 更大的文本:<big>
- 20% 更小的文本:<small>
- 设置字体属性:<font face=”font_family“ color=”hex_color”>。可能的字体系列示例包括
monospace
、serif
和sans_serif
。 - 设置等宽字体系列:<tt>
- 删除线:<s>、<strike>、<del>
- 下划线:<u>
- 上标:<sup>
- 下标:<sub>
- 项目符号:<ul>、<li>
- 换行符:<br>
- 分隔:<div>
- CSS 样式:<span style=”color|background_color|text-decoration”>
- 段落:<p dir=”rtl | ltr” style=”…”>
如果您没有应用格式,则可以通过调用 setText(java.lang.CharSequence)
直接设置 TextView 文本。但是,在某些情况下,您可能希望创建一个也用作格式字符串的样式文本资源。通常,这不起作用,因为 format(String, Object...)
和 getString(int, Object...)
方法会从字符串中剥离所有样式信息。解决此问题的办法是使用转义实体编写 HTML 标签,这些标签随后将在格式化完成后使用 fromHtml(String)
恢复。例如
- 将您的样式文本资源存储为 HTML 转义字符串
<resources> <string name="welcome_messages">Hello, %1$s! You have <b>%2$d new messages</b>.</string> </resources>
在此格式化字符串中,添加了
<b>
元素。请注意,左括号使用<
符号进行了 HTML 转义。 - 然后像往常一样格式化字符串,但也要调用
fromHtml(String)
将 HTML 文本转换为样式文本Kotlin
val text: String = getString(R.string.welcome_messages, username, mailCount) val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
Java
String text = getString(R.string.welcome_messages, username, mailCount); Spanned styledText = Html.fromHtml(text, FROM_HTML_MODE_LEGACY);
由于 fromHtml(String)
方法会格式化所有 HTML 实体,因此请确保在使用格式化文本的字符串中转义任何可能的 HTML 字符,使用 htmlEncode(String)
。例如,如果您正在格式化包含“<”或“&”之类的字符的字符串,则必须在格式化之前转义这些字符,这样当格式化后的字符串通过 fromHtml(String)
传递时,这些字符会以最初写入的方式显示出来。例如
Kotlin
val escapedUsername: String = TextUtils.htmlEncode
(username)
val text: String = getString(R.string.welcome_messages, escapedUsername, mailCount)
val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
Java
String escapedUsername = TextUtils.htmlEncode
(username);
String text = getString(R.string.welcome_messages, escapedUsername, mailCount);
Spanned styledText = Html.fromHtml(text);
使用 Spans 进行样式设置
一个 Spannable
是一个文本对象,您可以使用它对文本应用字体属性,例如颜色和字体粗细。您使用 SpannableStringBuilder
构建您的文本,然后将 android.text.style
包中定义的样式应用于文本。
您可以使用以下辅助方法来设置创建可跨越文本的大部分工作
Kotlin
/** * Returns a CharSequence that concatenates the specified array of CharSequence * objects and then applies a list of zero or more tags to the entire range. * * @param content an array of character sequences to apply a style to * @param tags the styled span objects to apply to the content * such as android.text.style.StyleSpan */ private fun apply(content: Array<out CharSequence>, vararg tags: Any): CharSequence { return SpannableStringBuilder().apply { openTags(tags) content.forEach { charSequence -> append(charSequence) } closeTags(tags) } } /** * Iterates over an array of tags and applies them to the beginning of the specified * Spannable object so that future text appended to the text will have the styling * applied to it. Do not call this method directly. */ private fun Spannable.openTags(tags: Array<out Any>) { tags.forEach { tag -> setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK) } } /** * "Closes" the specified tags on a Spannable by updating the spans to be * endpoint-exclusive so that future text appended to the end will not take * on the same styling. Do not call this method directly. */ private fun Spannable.closeTags(tags: Array<out Any>) { tags.forEach { tag -> if (length > 0) { setSpan(tag, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) } else { removeSpan(tag) } } }
Java
/** * Returns a CharSequence that concatenates the specified array of CharSequence * objects and then applies a list of zero or more tags to the entire range. * * @param content an array of character sequences to apply a style to * @param tags the styled span objects to apply to the content * such as android.text.style.StyleSpan * */ private static CharSequence applyStyles(CharSequence[] content, Object[] tags) { SpannableStringBuilder text = new SpannableStringBuilder(); openTags(text, tags); for (CharSequence item : content) { text.append(item); } closeTags(text, tags); return text; } /** * Iterates over an array of tags and applies them to the beginning of the specified * Spannable object so that future text appended to the text will have the styling * applied to it. Do not call this method directly. */ private static void openTags(Spannable text, Object[] tags) { for (Object tag : tags) { text.setSpan(tag, 0, 0, Spannable.SPAN_MARK_MARK); } } /** * "Closes" the specified tags on a Spannable by updating the spans to be * endpoint-exclusive so that future text appended to the end will not take * on the same styling. Do not call this method directly. */ private static void closeTags(Spannable text, Object[] tags) { int len = text.length(); for (Object tag : tags) { if (len > 0) { text.setSpan(tag, 0, len, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { text.removeSpan(tag); } } }
以下 bold
、italic
和 color
方法包装了上面的辅助方法,并展示了 android.text.style
包中定义的样式应用的具体示例。您可以创建类似的方法来执行其他类型的文本样式设置。
Kotlin
/** * Returns a CharSequence that applies boldface to the concatenation * of the specified CharSequence objects. */ fun bold(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.BOLD)) /** * Returns a CharSequence that applies italics to the concatenation * of the specified CharSequence objects. */ fun italic(vararg content: CharSequence): CharSequence = apply(content, StyleSpan(Typeface.ITALIC)) /** * Returns a CharSequence that applies a foreground color to the * concatenation of the specified CharSequence objects. */ fun color(color: Int, vararg content: CharSequence): CharSequence = apply(content, ForegroundColorSpan(color))
Java
/** * Returns a CharSequence that applies boldface to the concatenation * of the specified CharSequence objects. */ public static CharSequence bold(CharSequence... content) { return apply(content, new StyleSpan(Typeface.BOLD)); } /** * Returns a CharSequence that applies italics to the concatenation * of the specified CharSequence objects. */ public static CharSequence italic(CharSequence... content) { return apply(content, new StyleSpan(Typeface.ITALIC)); } /** * Returns a CharSequence that applies a foreground color to the * concatenation of the specified CharSequence objects. */ public static CharSequence color(int color, CharSequence... content) { return apply(content, new ForegroundColorSpan(color)); }
这是一个将这些方法链接在一起以将各种样式应用于短语中各个单词的示例
Kotlin
// Create an italic "hello, " a red "world", // and bold the entire sequence. val text: CharSequence = bold(italic(getString(R.string.hello)), color(Color.RED, getString(R.string.world)))
Java
// Create an italic "hello, " a red "world", // and bold the entire sequence. CharSequence text = bold(italic(getString(R.string.hello)), color(Color.RED, getString(R.string.world)));
core-ktx Kotlin 模块还包含扩展函数,这些函数使使用 Spans 变得更加容易。您可以在 GitHub 上查看 android.text 包文档以了解更多信息。
有关使用 Spans 的更多信息,请参阅以下链接
使用注释进行样式设置
您可以通过使用 Annotation
类以及 strings.xml 资源文件中的 <annotation>
标签来应用复杂或自定义样式。注释标签允许您通过在 XML 中定义自定义键值对来标记字符串的某些部分,然后框架将这些键值对转换为 Annotation
Spans。然后,您可以检索这些注释并使用键和值来应用样式。
创建注释时,请确保在每个 strings.xml 文件中将所有字符串的翻译中添加 <annotation>
标签。
在所有语言中将“text”一词应用自定义字体
示例 - 添加自定义字体
-
添加
<annotation>
标签,并定义键值对。在本例中,键为 font,值为我们要使用的字体类型:title_emphasis// values/strings.xml <string name="title">Best practices for <annotation font="title_emphasis">text</annotation> on Android</string> // values-es/strings.xml <string name="title"><annotation font="title_emphasis">Texto</annotation> en Android: mejores prácticas</string>
-
加载字符串资源并查找具有 font 键的注释。然后创建自定义 Span 并替换现有 Span。
Kotlin
// get the text as SpannedString so we can get the spans attached to the text val titleText = getText(R.string.title) as SpannedString // get all the annotation spans from the text val annotations = titleText.getSpans(0, titleText.length, Annotation::class.java) // create a copy of the title text as a SpannableString. // the constructor copies both the text and the spans. so we can add and remove spans val spannableString = SpannableString(titleText) // iterate through all the annotation spans for (annotation in annotations) { // look for the span with the key font if (annotation.key == "font") { val fontName = annotation.value // check the value associated to the annotation key if (fontName == "title_emphasis") { // create the typeface val typeface = getFontCompat(R.font.permanent_marker) // set the span at the same indices as the annotation spannableString.setSpan(CustomTypefaceSpan(typeface), titleText.getSpanStart(annotation), titleText.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } } } // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan styledText.text = spannableString
Java
// get the text as SpannedString so we can get the spans attached to the text SpannedString titleText = (SpannedString) getText(R.string.title); // get all the annotation spans from the text Annotation[] annotations = titleText.getSpans(0, titleText.length(), Annotation.class); // create a copy of the title text as a SpannableString. // the constructor copies both the text and the spans. so we can add and remove spans SpannableString spannableString = new SpannableString(titleText); // iterate through all the annotation spans for (Annotation annotation: annotations) { // look for the span with the key font if (annotation.getKey().equals("font")) { String fontName = annotation.getValue(); // check the value associated to the annotation key if (fontName.equals("title_emphasis")) { // create the typeface Typeface typeface = ResourcesCompat.getFont(this, R.font.roboto_mono); // set the span at the same indices as the annotation spannableString.setSpan(new CustomTypefaceSpan(typeface), titleText.getSpanStart(annotation), titleText.getSpanEnd(annotation), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } } // now, the spannableString contains both the annotation spans and the CustomTypefaceSpan styledText.text = spannableString;
如果您多次使用相同的文本,则应只构造一次 SpannableString 对象并在需要时重复使用它,以避免潜在的性能和内存问题。
有关注释用法的更多示例,请参阅 Android 中的国际化文本样式设置
注释 Spans 和文本分块
由于 Annotation
Spans 也是 ParcelableSpans
,因此键值对会被分块和取消分块。只要包裹的接收方知道如何解释注释,您就可以使用 Annotation
Spans 对包裹的文本应用自定义样式。
要将文本传递给 Intent Bundle 时保留自定义样式,您首先需要将 Annotation
Spans 添加到文本中。您可以在 XML 资源中通过 <annotation> 标签执行此操作,如上面的示例所示,或者在代码中通过创建一个新的 Annotation
并将其设置为 Span 来执行此操作,如下所示
Kotlin
val spannableString = SpannableString("My spantastic text") val annotation = Annotation("font", "title_emphasis") spannableString.setSpan(annotation, 3, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) // start Activity with text with spans val intent = Intent(this, MainActivity::class.java) intent.putExtra(TEXT_EXTRA, spannableString) startActivity(intent)
Java
SpannableString spannableString = new SpannableString("My spantastic text"); Annotation annotation = new Annotation("font", "title_emphasis"); spannableString.setSpan(annotation, 3, 7, 33); // start Activity with text with spans Intent intent = new Intent(this, MainActivity.class); intent.putExtra(TEXT_EXTRA, spannableString); this.startActivity(intent);
从 Bundle
中检索文本作为 SpannableString
,然后解析附加的注释,如上面的示例所示。
Kotlin
// read text with Spans val intentCharSequence = intent.getCharSequenceExtra(TEXT_EXTRA) as SpannableString
Java
// read text with Spans SpannableString intentCharSequence = (SpannableString)intent.getCharSequenceExtra(TEXT_EXTRA);
有关文本样式的更多信息,请参阅以下链接