Android复习(二)应用资源——>字符串
转自:https://developer.android.google.cn/guide/topics/resources/string-resource#kotlin
字符串资源为您的应用提供具有可选文本样式和格式设置的文本字符串。共有三种类型的资源可为您的应用提供字符串:
- String
- 提供单个字符串的 XML 资源。
- String Array
- 提供字符串数组的 XML 资源。
- Quantity Strings (Plurals)
- 带有用于多元化的不同字符串的 XML 资源。
所有字符串都能应用某些样式设置标记和格式设置参数。如需了解有关样式和格式设置字符串的信息,请参阅格式和样式设置部分。
String
可从应用或其他资源文件(如 XML 布局)引用的单个字符串。
请注意:字符串是一种简单资源,您可以使用 name
属性(并非 XML 文件的名称)中提供的值对其进行引用。因此,您可以在一个 <resources>
元素下,将字符串资源与其他简单资源合并到一个 XML 文件中。
- 文件位置:
res/values/filename.xml
filename 是任意值。<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" />
以下应用代码用于检索字符串:
java代码: String string = getString(R.string.hello); kotlin代码: val string: String = getString(R.string.hello)
您可以使用
getString(int)
或getText(int)
检索字符串。getText(int)
会保留所有应用于字符串的富文本样式。
String Array
可从应用引用的字符串数组。
请注意:字符串数组是一种简单资源,您可以使用 name
属性(并非 XML 文件的名称)中提供的值对其进行引用。因此,您可以在一个 <resources>
元素下,将字符串数组资源与其他简单资源合并到一个 XML 文件中。
- 文件位置:
res/values/filename.xml
filename 是任意值。<string-array>
元素的name
用作资源 ID。- 编译资源的数据类型:
- 指向
String
数组的资源指针。 - 资源引用:
- 在 Java 中:
R.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>
以下应用代码用于检索字符串数组:
java代码: Resources res = getResources(); String[] planets = res.getStringArray(R.array.planets_array); kotlin代码: val array: Array = resources.getStringArray(R.array.planets_array)
Quantity Strings (Plurals)
针对语法数量的一致性,不同语言有不同规则。例如,在英语中,数量 1 是一种特殊情况。我们会写成“1 book”,但如果是任何其他数量,则会写成“n books”。这种对单复数的区分很常见,但其他语言拥有更细致的区分。Android 支持以下完整集合:zero
、one
、two
、few
、many
和 other
。
决定为给定语言和数量使用哪种情况的规则可能非常复杂,因此 Android 为您提供 getQuantityString()
等方法来选择合适资源。
虽然 Quantity Strings 过去称作“Quantity Strings”(并且 API 中仍采用此名称),但其只应用于表示复数。例如,类似使用 Quantity Strings 实现 Gmail 的“Inbox”这类情况便属于错误行为,正确的做法是用其实现“Inbox (12)”这种存在未读邮件的情况。使用 Quantity Strings 来替代 if
语句似乎很方便,但必须注意的是,某些语言(如中文)根本不做这些语法区分,因此您获取的始终是 other
字符串。
选择使用哪一个字符串完全取决于语法上的必要性。在英语中,即使数量为 0,表示 zero
的字符串也会被忽略,因为在语法上,0 与 2 或除 1 以外的任何其他数字并无区别(“zero books”、“one book”、“two books”等)。相反,韩语中仅使用过 other
字符串。
请勿被某些事实误导,例如 two
听起来仅适用于数量 2:某种语言可能规定,对 2、12、102(依此类推)等数量进行相同处理,但对其他数量进行特殊处理。您可以依靠翻译人员来了解其语言的实际区分要求。
通常,您可以利用“Books: 1”等无需考虑数量的表示,从而避免使用 Quantity Strings。如果您的应用可接受此样式,则您和翻译人员的工作都会更轻松。
请注意:Plurals 集合是一种简单资源,您可以使用 name
属性(并非 XML 文件的名称)中提供的值对其进行引用。因此,您可以在一个 <resources>
元素下,将 plurals 资源与其他简单资源合并到一个 XML 文件中。
- 文件位置:
res/values/filename.xml
filename 是任意值。<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>
保存在
res/values-pl/strings.xml
中的 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>
用法:
java代码: int count = getNumberOfSongsAvailable(); Resources res = getResources(); String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count); kotlin代码: val count = getNumberOfSongsAvailable() val songsFound = resources.getQuantityString(R.plurals.numberOfSongsAvailable, count, count)
使用
getQuantityString()
方法时,如果您的字符串包含带有数字的字符串格式设置,则您需要传递两次count
。例如,对于字符串%d songs found
,第一个count
参数会选择相应的复数字符串,第二个count
参数会被插入%d
占位符内。如果您的复数字符串没有字符串格式设置,则无需向getQuantityString
传递第三个参数。
格式和样式
关于如何正确设置字符串资源的格式和样式,您应了解以下几个要点。
处理特殊字符
如果 XML 或 Android 中的字符串包含有特殊用法的字符,则必须转义这些字符。您可以使用前导反斜杠转义某些字符,但其他字符需使用 XML 转义。您也可以通过在双引号中包括整个字符串,处理撇号和单引号。以下为部分示例:
字符 | 转义形式 |
---|---|
@ | \@ |
? | \? |
< | < |
& | & |
单引号 (' ) |
以下任意字符:
|
双引号 (" ) |
以下任意字符:
请注意,您必须转义双引号。在单引号中包括字符串没有任何作用。 |
设置字符串格式
如需设置字符串的格式,您可以在字符串资源中放入格式参数(如以下示例资源所示)。
<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>
在本例中,格式字符串有两个参数:%1$s
为字符串,而 %2$d
为十进制数字。然后,您可通过调用 getString(int, Object...)
来设置字符串格式。例如:
使用 HTML 标记设置样式
您可以使用 HTML 标记为字符串添加样式设置。例如:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="welcome">Welcome to <b>Android</b>!</string> </resources>
支持以下 HTML 元素:
- 粗体:<b>、<em>
- 斜体:<i>、<cite>、<dfn>
- 文本放大 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 文本转换成带样式的文本:java代码: String text = getString(R.string.welcome_messages, username, mailCount); Spanned styledText = Html.fromHtml(text, FROM_HTML_MODE_LEGACY); kotlin代码: val text: String = getString(R.string.welcome_messages, username, mailCount) val styledText: Spanned = Html.fromHtml(text, FROM_HTML_MODE_LEGACY)
由于 fromHtml(String)
方法会设置所有 HTML 实体的格式,因此请务必使用 htmlEncode(String)
转义带格式文本的字符串中任何可能存在的 HTML 字符。例如,如果您打算对包含“<”或“&”等字符的字符串进行格式设置,则在设置格式前必须先转义这类字符。如此一来,当通过 fromHtml(String)
传递带格式的字符串时,字符才会以最初的编写形式显示。例如:
java代码: String escapedUsername = TextUtils.htmlEncode(username); String text = getString(R.string.welcome_messages, escapedUsername, mailCount); Spanned styledText = Html.fromHtml(text); 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)
使用 Spannable 设置样式
Spannable
是一种文本对象,您可使用颜色和字体粗细等字体属性对其进行样式设置。您可以使用 SpannableStringBuilder
生成文本,然后对文本应用 android.text.style
软件包中定义的样式。
您可以使用以下辅助方法设置创建 spannable 文本的大量工作:
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); } } } 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) } } }
以下 bold
、italic
和 color
方法包含上述辅助方法,并展示应用 android.text.style
软件包中所定义样式的具体示例。通过创建类似的方法,您也可对其他类型的文本进行样式设置。
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代码: /** * 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代码: // Create an italic "hello, " a red "world", // and bold the entire sequence. var text = bold(italic(getString(R.string.hello)), color(Color.RED, getString(R.string.world))) </pre> </section><section><h3 id="java">Java</h3> <pre class="prettyprint lang-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))); 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)))
core-ktx Kotlin 模块还包含扩展函数,便于您更轻松地使用 span。您可以前往 GitHub 查看 android.text 软件包文档,了解详情。
如需了解有关使用 span 的更多信息,请访问以下链接:
使用注解设置样式
您可以通过使用 strings.xml 资源文件中的 Annotation
类和 <annotation>
标记,应用复杂样式或自定义样式。借助注解标记,您可以通过在 XML 文件中定义自定义键值对来标记自定义样式的部分字符串,框架随后会将该 XML 文件转换成 Annotation
span。然后,您便可检索这些注解,并使用键和值来应用样式。
创建注解时,请务必为 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。
java代码: // get the text as SpannedString so we can get the spans attached to the text SpannedString titleText = (SpannedString) getText(R.string.title_about); // 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; 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
如果您多次使用相同文本,则应构建一次 SpannableString 对象并根据需要重复使用该对象,以避免出现潜在的性能和内存问题。
如需了解注解用法的更多示例,请参阅在 Android 中设置国际化文本的样式
注解 span 和文本打包
Annotation
span 也是 ParcelableSpans
,因此需对键值对进行打包和拆包。只要包的接收方了解如何解释注解,您便可使用 Annotation
span 向打包文本应用自定义样式。
如要在向 Intent Bundle 传递文本时保留自定义样式,您首先需在文本中添加 Annotation
span。您可以使用 <annotation> 标记在 XML 资源中执行此操作(如上例所示),或通过创建新的 Annotation
并将其设置为 span,在代码中执行此操作(如下所示):
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); 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)
以 SpannableString
的形式从 Bundle
中检索文本,然后解析附加的注解(如上例所示)。
java代码: // read text with Spans SpannableString intentCharSequence = (SpannableString)intent.getCharSequenceExtra(TEXT_EXTRA); kotlin代码: // read text with Spans val intentCharSequence = intent.getCharSequenceExtra(TEXT_EXTRA) as SpannableString