文字列リソースは、アプリにテキスト文字列と、オプションのテキストのスタイルやフォーマットを提供します。アプリに文字列を提供できるリソースには、次の 3 つのタイプがあります。
- 文字列
- 単一の文字列を提供する XML リソース。
- 文字列配列
- 文字列配列を提供する XML リソース。
- 数量文字列(複数形)
- 複数形を表すさまざまな文字列を保有する XML リソース。
どの文字列にも、スタイル設定のマークアップと書式設定引数を適用できます。文字列のスタイル設定と書式設定については、書式設定とスタイル設定をご覧ください。
文字列
アプリケーションや他のリソース ファイル(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
属性で提供される値を使用して参照されるシンプルなリソースです(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>次のアプリコードは、文字列配列を取得します。
val array: Array<String> =
resources
.getStringArray
(R.array.planets_array)Resources res =
getResources()
;
String[] planets = res.getStringArray
(R.array.planets_array);
数量文字列(複数形)
言語によって、数量の文法上の運用に関するルールは異なります。英語では、たとえば数量 1 が特殊なケースです。1 の場合は「1 book」と記述しますが、その他の数量については「n books」と記述します。このような単数形と複数形の区別は一般的なものですが、さらに細かく区別する言語もあります。Android でサポートされている区分は、zero
、one
、two
、few
、many
、other
です。
特定の言語および数量にどの区分が当てはまるかを決定するルールは非常に複雑になる可能性があるため、Android では getQuantityString()
などのメソッドによって、適切なリソースが選択されるようになっています。
数量文字列は慣例的に、また API では現在も「数量」文字列と呼ばれていますが、実際には複数形の処理にのみ使用します。たとえば、Gmail の「受信トレイ」を、未読メッセージがある場合に「受信トレイ (12)」などと表示するための実装に数量文字列を使用するのは間違いです。if
ステートメントの代わりに数量文字列を使用する方が便利にも見えますが、一部の言語(中国語など)では単複の文法的な区別が行われないため、常に other
文字列が取得されます。
使用する文字列は、文法上の必要性のみに基づいて選択されます。英語における 0 は、2 やその他 1 を除く数字と文法的な違いはないため(「zero books」、「one book」、「two books」となる)、数量が 0 であっても zero
の文字列は無視されます。これに対して韓国語では、other
という文字列だけが使用されます。
また、two
は数量 2 のみに適用されるように思えますが、言語によっては、2、12、102 などが同じルールで扱われ、その他の数量とは区別される場合があります。各言語でどのような区別が求められるかは、翻訳者が判断することになります。
メッセージに数値が含まれていない場合、複数形の使用には適さない可能性があります。たとえば、リトアニア語では単数形が 1 と 101 の両方に使用されるため、「1 book」は「1 knyga」、「101 books」は「101 knyga」に翻訳されます。一方、「a book」は「knyga」で、「many books」は「daug knygų」になります。英語の複数形のメッセージに「a book」(単数形)と「many books」(複数形)が含まれていて、数字がない場合は、「knyga」(a book)/「daug knygų」(many books)と翻訳しますが、リトアニア語の文法では、101 という数値があった場合でも、1 冊の本を表す「knyga」と記載されます。
多くの場合、「Books: 1」のような数量に依存しない形式にすることで、数量文字列を回避できます。このスタイルをアプリで使用できれば、その後のアプリの管理や、翻訳時の負担が軽減されます。
API 24 以降では、さらに強力な ICU MessageFormat
クラスを使用できます。
注: 複数形コレクションは、name
属性で提供される値を使用して参照されるシンプルなリソースです(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>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>使用方法:
val count = getNumberOfSongsAvailable()
val songsFound = resources.getQuantityString
(R.plurals.numberOfSongsAvailable, count, count)int count = getNumberOfSongsAvailable();
Resources res =getResources()
;
String songsFound = res.getQuantityString
(R.plurals.numberOfSongsAvailable, count, count);getQuantityString()
メソッドを使用する際、文字列に数値の文字列形式が含まれる場合は、count
を 2 回渡す必要があります。たとえば、%d songs found
という文字列の場合、最初のcount
パラメータが適切な複数形の文字列を選択し、2 つ目のcount
パラメータが%d
プレースホルダに挿入されます。複数形の文字列に文字列形式が含まれていない場合、3 つ目のパラメータをgetQuantityString
に渡す必要はありません。
書式とスタイル
ここでは、文字列リソースの書式とスタイルを適切に設定する方法について説明します。
特殊文字の処理
XML で特別な意味を持つ文字が文字列に含まれている場合は、それらの文字を XML / HTML の標準のエスケープ ルールに従ってエスケープする必要があります。Android で特別な意味を持つ文字をエスケープする必要がある場合は、該当する文字の前にバックスラッシュを置きます。
Android ではデフォルトで、連続する空白文字が 1 つのスペースに短縮されます。これを回避するには、文字列の該当する部分を二重引用符で囲みます。それにより、引用符で囲んだ部分の空白文字(改行を含む)が保持されます。この二重引用符により、エスケープされない標準の単一引用符を使用することも可能になります。
文字 | エスケープ書式 |
---|---|
@ | \@ |
? | \? |
改行 | \n |
Tab | \t |
U+XXXX Unicode 文字 | \uXXXX |
単一引用符(' ) |
次のいずれか:
|
二重引用符(" ) |
\"
文字列を単一引用符で囲んでも機能しません。 |
空白の短縮と Android のエスケープは、リソース ファイルが XML として解析された後に行われます。つまり、<string>      </string>
(スペース、句読点スペース、Unicode Em スペース)は、ファイルが XML として解析されるとすべて Unicode のスペースになるため、すべてが 1 つのスペース(" "
)に短縮されます。それらのスペースを保持するには、スペースを引用符で囲む(<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>
この例では、書式設定文字列に 2 つの引数があります。%1$s
は文字列、%2$d
は 10 進数です。次に、getString(int, Object...)
を呼び出して文字列を書式設定します。次に例を示します。
var text = getString(R.string.welcome_messages, username, mailCount)
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 テキストをスタイル付きテキストに変換します。
fromHtml(String)
メソッドによってすべての HTML エンティティの書式が設定されるため、書式設定されたテキストで使用する文字列に含まれる HTML 文字は、htmlEncode(String)
を使用して必ずエスケープしてください。たとえば、「<」や「&」などの文字が含まれる文字列を書式設定する場合は、その前にエスケープする必要があります。それにより、書式設定された文字列が fromHtml(String)
を介して渡され、最初に記述したとおりに表示されます。次に例を示します。
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)
String escapedUsername = TextUtils.htmlEncode
(username);
String text = getString(R.string.welcome_messages, escapedUsername, mailCount);
Spanned styledText = Html.fromHtml(text);
Spannable を使用したスタイル設定
Spannable
は、色やフォントの太さなどの書体プロパティを使用してスタイル設定できるテキスト オブジェクトです。SpannableStringBuilder
を使用してテキストを作成し、android.text.style
パッケージで定義したスタイルをテキストに適用します。
次のヘルパー メソッドによって、Spannable テキストの作成作業の多くを処理できます。
/**
* 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)
}
}
}
/**
* 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
パッケージで定義されたスタイルを適用する例を示しています。同様のメソッドを作成して、他のタイプのテキストのスタイル設定を行うこともできます。
/**
* 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))
/**
* 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));
}
次に、これらのメソッドを連鎖させてフレーズ内の個々の単語にさまざまなスタイルを適用する方法の例を示します。
// 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)))
// 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 モジュールには、スパンの処理をさらに簡単にする拡張関数が含まれています。詳しくは、GitHub の android.text パッケージ ドキュメントをご覧ください。
スパンの処理について詳しくは、次のリンクをご覧ください。
アノテーションを使用したスタイル設定
複雑なスタイル設定やカスタム スタイル設定を適用するには、strings.xml リソース ファイルで Annotation
クラスと <annotation>
タグを使用します。アノテーション タグを使用して文字列の一部にマークを付けることで、XML でカスタム Key-Value ペアを定義し、カスタム スタイル設定を行うことができます。これはフレームワークによって Annotation
スパンに変換されます。これらのアノテーションを取得し、キーと値を使用してスタイルを適用します。
アノテーションを作成する際には、すべての strings.xml ファイルで、文字列のすべての翻訳に <annotation>
タグを追加してください。
すべての言語の「text」という単語にカスタム書体を適用する
例 - カスタム書体の追加
-
<annotation>
タグを追加し、Key-Value ペアを定義します。この場合、キーは 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 キーでアノテーションを特定します。次にカスタムスパンを作成して、既存のスパンと置き換えます。
// 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// 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 オブジェクトを 1 回作成し、必要に応じて再利用することで、潜在的なパフォーマンスとメモリの問題を回避できます。
アノテーションの使用例については、Android での国際化されたテキストのスタイル設定をご覧ください。
アノテーション スパンとテキストのパーセリング
Annotation
スパンも ParcelableSpans
であるため、Key-Value ペアはパーセル化 / パーセル化解除が可能です。パーセルの受信者がアノテーションの解釈方法を理解していれば、Annotation
スパンを使用して、パーセル化されたテキストにカスタム スタイルを適用できます。
インテント バンドルにテキストを渡す際にカスタム スタイルを保持するには、まず Annotation
スパンをテキストに追加する必要があります。それには、上の例のように XML リソースで <annotation> タグを使用するか、次のようにコード内で新しく Annotation
を作成してスパンとして設定します。
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 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
として取得してから、上記の例に示すように、添付されているアノテーションを解析します。
// read text with Spans
val intentCharSequence = intent.getCharSequenceExtra(TEXT_EXTRA) as SpannableString
// read text with Spans
SpannableString intentCharSequence = (SpannableString)intent.getCharSequenceExtra(TEXT_EXTRA);
テキストのスタイル設定について詳しくは、次のリンクをご覧ください。