web-dev-qa-db-ja.com

SpannableとString.format()の組み合わせ

次の文字列があるとします。

String s = "The cold hand reaches for the %1$s %2$s Ellesse's";
String old = "old"; 
String tan = "tan"; 
String formatted = String.format(s,old,tan); //"The cold hand reaches for the old tan Ellesse's"

最終的にこの文字列になりたいが、特定の SpanString.formatに置き換えられたWordに設定されているとします。

たとえば、次のことも行います。

Spannable spannable = new SpannableString(formatted);
spannable.setSpan(new StrikethroughSpan(), oldStart, oldStart+old.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannable.setSpan(new ForegroundColorSpan(Color.BLUE), tanStart, tanStart+tan.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

oldtanの開始インデックスを知るための強力な方法はありますか?

'old'を検索するだけでは、 'cold'の 'old'が返されるため、機能しないことに注意してください。

willが機能するのは、事前に%[0-9]$sを検索し、String.formatの置換を考慮してオフセットを計算することだと思います。 。これは頭​​痛の種のようですが、フォーマットの詳細についてより有益なString.formatのようなメソッドがあるのではないかと思います。さて、ありますか?

25
Maarten

そのようなSpannablesを使用することは頭痛の種です-これはおそらく最も簡単な方法です:

String s = "The cold hand reaches for the %1$s %2$s Ellesse's";
String old = "<font color=\"blue\">old</font>"; 
String tan = "<strike>tan</strike>"; 
String formatted = String.format(s,old,tan); //The cold hand reaches for the <font color="blue">old</font> <strike>tan</strike> Ellesse's

Spannable spannable = Html.fromHtml(formatted);

問題:これはStrikethroughSpanを入れません。 StrikethroughSpanを作成するには、 この質問 からカスタムTagHandlerを借用します。

Spannable spannable = Html.fromHtml(text,null,new MyHtmlTagHandler());

MyTagHandler:

public class MyHtmlTagHandler implements Html.TagHandler {
    public void handleTag(boolean opening, String tag, Editable output,
                          XMLReader xmlReader) {
        if (tag.equalsIgnoreCase("strike") || tag.equals("s")) {
            processStrike(opening, output);
        }
    }
    private void processStrike(boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, StrikethroughSpan.class);
            int where = output.getSpanStart(obj);
            output.removeSpan(obj);
            if (where != len) {
                output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);
        if (objs.length == 0) {
            return null;
        } else {
            for (int i = objs.length; i > 0; i--) {
                if (text.getSpanFlags(objs[i - 1]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i - 1];
                }
            }
            return null;
        }
    }
}
13
Maarten

スパナブルで動作する String.format のバージョンを作成しました。ダウンロードして、通常のバージョンと同じように使用してください。あなたの場合、フォーマット指定子の周りにスパンを配置します(おそらくstrings.xmlを使用します)。出力では、それらはそれらの指定子が置き換えられたものの周りにあります。

15
George Steel

String.formatにまたがる問題を解決する簡単なKotlin拡張関数を作成しました。

fun Context.getStringSpanned(@StringRes resId: Int, vararg formatArgs: Any): Spanned {
    var lastArgIndex = 0
    val spannableStringBuilder = SpannableStringBuilder(getString(resId, *formatArgs))
    for (arg in formatArgs) {
        val argString = arg.toString()
        lastArgIndex = spannableStringBuilder.indexOf(argString, lastArgIndex)
        if (lastArgIndex != -1) {
            (arg as? CharSequence)?.let {
                spannableStringBuilder.replace(lastArgIndex, lastArgIndex + argString.length, it)
            }
            lastArgIndex += argString.length
        }
    }

    return spannableStringBuilder
}
4
Pawel Cala

同じ問題がポップアップしましたが、ここで本当に素晴らしい解決策を見つけました: Android:Spannable.setSpanをString.formatと組み合わせる方法?

@ george-steelが提供するソリューションを確認してください。彼はスパンを保持する String.formatのカスタムバージョン を作成しました。

使用例:

Spanned toDisplay = SpanFormatter.format(getText(R.string.foo), bar, baz, quux);
0
Chuck Taylor