web-dev-qa-db-ja.com

Androidでフォーマットされた文字列をプレースホルダーと一緒に使用するにはどうすればよいですか?

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など)に渡すことができ、マークされたフレーズには下線が付きます。

ただし、フォーマットされた文字列とプレースホルダーを使用して、これら2つの方法を組み合わせる方法を見つけることができませんでした。

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

上記のリソースをJavaコードで処理してTextViewに表示し、_%1$d_を整数に置き換えますか?

44
maral

最後に、実用的なソリューションを見つけて、プレースホルダーを置き換えるための独自の方法を記述し、フォーマットを維持しました。

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タグをエスケープする必要はありません。

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

16
Wagner Michael

数値フォーマットなしのプレースホルダー(つまり、先行ゼロ、コンマの後の数値)を置き換える単純なケースでは、Square Phrase ライブラリーを使用できます。

使用方法は非常に簡単です。最初に、文字列リソースのプレースホルダーを次の簡単な形式に変更する必要があります。

_<string name="underlined_number">My number is <u> {number} </u></string>
_

次に、このように置き換えることができます:

_CharSequence formatted = Phrase.from(getResources(), R.string.underlined_number)
   .put("number", 5)
   .format()
_

フォーマットされたCharSequenceもスタイル設定されます。数値をフォーマットする必要がある場合は、いつでもString.format("%03d", 5)を使用して数値を事前フォーマットし、その結果の文字列を.put()関数で使用できます。

8
Genma

その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)
}
7
Frank Harper

受け入れられた回答と同様に、私はこのためにKotlin拡張メソッドを作成しようとしました。

これがKotlinで受け入れられた答えです

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

受け入れられた回答の問題は、フォーマット引数自体がスタイル設定されている(つまり、ストリングではなくスパンされている)場合に機能しないように見えることです。実験的に、奇妙なことをしているようです。おそらく、文字列以外のCharSequenceをエスケープしていないという事実に関係しています。電話すると

_context.getText(R.id.my_format_string, myHelloSpanned)
_

ここで、R.id.my_format_stringは次のとおりです。

_<string name="my_format_string">===%1$s===</string>
_

そしてmyHelloSpannedは<b> hello </ b>のように見えるスパンドです(つまり、HTML _<i>&lt;b&gt;hello&lt;/b&gt;</i>_になるでしょう)= == hello===(つまり、HTML _===<b>hello</b>===_)。

それは間違っています、===<b> hello </ b>===を取得する必要があります。

_String.format_を適用する前にすべてのCharSequencesをHTMLに変換することでこれを修正しようとしましたが、これが結果のコードです。

_@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))myHelloSpannedと等しくありません-追加のパディングがあります。これをうまく解決する方法を知りませんでした。

3
Chrispher

廃止されたAPIを使用せず、すべてのAndroid=バージョンで機能し、CDATAセクションで文字列をラップする必要がない、より読みやすいKotlin拡張機能を次に示します。

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

フラグメントの拡張としてエイリアスを追加できます-間に引数を分散することを忘れないでください:

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