文字列リソース(ビュー)

コンセプトと Jetpack Compose の実装

文字列リソースは、アプリにテキスト文字列と、オプションのテキストのスタイルやフォーマットを提供します。アプリに文字列を提供できるリソースには、次の 3 つのタイプがあります。

String
単一の文字列を提供する XML リソース。
文字列配列
文字列配列を提供する XML リソース。
数量文字列(複数形)
複数形を表すさまざまな文字列を保有する XML リソース。

どの文字列にも、スタイル設定のマークアップと書式設定引数を適用できます。文字列のスタイル設定と書式設定については、書式設定とスタイル設定をご覧ください。

文字列

アプリケーションや他のリソース ファイル(XML レイアウトなど)から参照できる単一の文字列。

ファイルの場所:
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>
要素:
<resources>
必須。ルートノードにする必要があります。

属性はありません。

<string>
文字列。スタイル設定タグを含めることができます。アポストロフィや引用符はエスケープする必要があります。文字列のスタイルと書式を適切に設定する方法については、後述の書式設定とスタイル設定をご覧ください。

属性:

name
文字列。文字列の名前。この名前がリソース ID として使用されます。
例:
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" />

次のアプリコードは、文字列を取得します。

Kotlin

val string: String = getString(R.string.hello)

Java

String string = getString(R.string.hello);

文字列を取得するには、getString(int) または getText(int) を使用します。getText(int) は、文字列に適用されたすべてのリッチテキストのスタイル設定を保持します。

文字列配列

アプリから参照可能な文字列の配列。

ファイルの場所:
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>
要素:
<resources>
必須。ルートノードにする必要があります。

属性はありません。

<string-array>
文字列の配列を定義します。1 つまたは複数の <item> 要素を格納します。

属性:

name
文字列。配列の名前。この名前は、配列を参照するためのリソース ID として使用されます。
<item>
文字列。スタイル設定タグを含めることができます。この値は、別の文字列リソースへの参照として設定できます。<string-array> 要素の子を指定する必要があります。アポストロフィや引用符はエスケープする必要があります。文字列の適切なスタイル設定と書式設定については、以下の書式設定とスタイル設定をご覧ください。

属性はありません。

例:
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 の場合は「1 book」と記述しますが、その他の数量については「n books」と記述します。このような単数形と複数形の区別は一般的なものですが、さらに細かく区別する言語もあります。Android でサポートされている区分は、zeroonetwofewmanyother です。

特定の言語および数量にどの区分が当てはまるかを決定するルールは非常に複雑になる可能性があるため、Android では getQuantityString() などのメソッドによって、適切なリソースが選択されるようになっています。

API 24 以降では、さらに強力な ICU MessageFormat クラスを使用できます。

ファイルの場所:
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>
要素:
<resources>
必須。ルートノードにする必要があります。

属性はありません。

<plurals>
文字列のコレクション。ものの量に応じてこのうち 1 つの文字列が提供されます。1 つまたは複数の <item> 要素を格納します。

属性:

name
文字列。文字列のペアの名前。この名前がリソース ID として使用されます。
</dd>

<item>
複数形または単数形の文字列。この値は、別の文字列リソースへの参照として設定できます。<plurals> 要素の子を指定する必要があります。アポストロフィや引用符はエスケープする必要があります。文字列の適切なスタイル設定と書式設定については、以下の書式設定とスタイル設定をご覧ください。

属性:

quantity
キーワード。この文字列を使用するタイミングを示す値。値が有効となる場合の例を丸かっこ内で示しています。
説明
zero数字の 0 の処理が特殊である言語(アラビア語など)。
one1 などの数字の処理が特殊である言語(英語をはじめとする大半の言語の 1 が該当。ロシア語の場合は、末尾が 1 であり、かつ 11 でない数字がこのクラスに該当)。
two2 などの数字の処理が特殊である言語(ウェールズ語の 2、スロベニア語の 102 など)。
few「小さな」数字の処理が特殊である言語(チェコ語の 2、3、4 や、ポーランド語の 12、13、14 を除く、末尾が 2、3、4 の数字など)。
many「大きな」数字の処理が特殊である言語(マルタ語の末尾が 11~99 の数字など)。
other数量について特殊な処理を必要としない言語(中国語のすべての数字、英語の 42 など)。

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

使用方法:

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 を 2 回渡す必要があります。たとえば、%d songs found という文字列の場合、最初の count パラメータが適切な複数形の文字列を選択し、2 つ目の count パラメータが %d プレースホルダに挿入されます。複数形の文字列に文字列形式が含まれていない場合、3 つ目のパラメータを getQuantityString に渡す必要はありません。

書式とスタイル

ここでは、文字列リソースの書式とスタイルを適切に設定する方法について説明します。

文字列の書式設定

文字列の書式を設定する場合は、次のリソース例に示すように、文字列リソースに書式の引数を指定します。

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

この例では、書式設定文字列に 2 つの引数があります。%1$s は文字列、%2$d は 10 進数です。次に、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>

書式設定を適用していない場合は、setText(java.lang.CharSequence) を呼び出して TextView テキストを直接設定できます。ただし、場合によっては、書式設定文字列としても使用されるスタイル付きのテキスト リソースを作成することがあります。format(String, Object...) メソッドと getString(int, Object...) メソッドによって文字列からすべてのスタイル情報が削除されるため、これは通常機能しません。これを回避するには、エスケープ処理したエンティティを使用して HTML タグを記述します。エンティティは書式設定の後で fromHtml(String) によって復元されます。次に例を示します。

  1. スタイル付きのテキスト リソースを HTML エスケープ文字列として保存します。
    <resources>
      <string name="welcome_messages">Hello, %1$s! You have &lt;b>%2$d new messages&lt;/b>.</string>
    </resources>

    この書式設定された文字列では、<b> 要素が追加されています。左かっこは &lt; 表記によって HTML エスケープされています。

  2. 次に、文字列を通常どおり書式設定し、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);

Spannable を使用したスタイル設定

Spannable は、色やフォントの太さなどの書体プロパティを使用してスタイル設定できるテキスト オブジェクトです。SpannableStringBuilder を使用してテキストを作成し、android.text.style パッケージで定義したスタイルをテキストに適用します。

次のヘルパー メソッドによって、Spannable テキストの作成作業の多くを処理できます。

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

以下の bolditaliccolor メソッドは、上記のヘルパー メソッドをラップして、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 モジュールには、スパンの処理をさらに簡単にする拡張関数が含まれています。詳しくは、GitHub の android.text パッケージ ドキュメントをご覧ください。

スパンの処理について詳しくは、次のリンクをご覧ください。

アノテーションを使用したスタイル設定

複雑なスタイル設定やカスタム スタイル設定を適用するには、strings.xml リソース ファイルで Annotation クラスと <annotation> タグを使用します。アノテーション タグを使用して文字列の一部にマークを付けることで、XML でカスタム Key-Value ペアを定義し、カスタム スタイル設定を行うことができます。これはフレームワークによって Annotation スパンに変換されます。これらのアノテーションを取得し、キーと値を使用してスタイルを適用します。

アノテーションを作成する際には、すべての strings.xml ファイルで、文字列のすべての翻訳に <annotation> タグを追加してください。


すべての言語の「text」という単語にカスタム書体を適用する

例 - カスタム書体の追加

  1. <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>
  2. 文字列リソースを読み込み、font キーでアノテーションを特定します。次にカスタムスパンを作成して、既存のスパンと置き換えます。

    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 オブジェクトを 1 回作成し、必要に応じて再利用することで、潜在的なパフォーマンスとメモリの問題を回避できます。

アノテーションの使用例については、Android での国際化されたテキストのスタイル設定をご覧ください。

アノテーション スパンとテキストのパーセリング

Annotation スパンも ParcelableSpans であるため、Key-Value ペアはパーセル化 / パーセル化解除が可能です。パーセルの受信者がアノテーションの解釈方法を理解していれば、Annotation スパンを使用して、パーセル化されたテキストにカスタム スタイルを適用できます。

インテント バンドルにテキストを渡す際にカスタム スタイルを保持するには、まず Annotation スパンをテキストに追加する必要があります。それには、上の例のように XML リソースで <annotation> タグを使用するか、次のようにコード内で新しく Annotation を作成してスパンとして設定します。

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

テキストのスタイル設定について詳しくは、次のリンクをご覧ください。