次の文字列があるとします。
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"
最終的にこの文字列になりたいが、特定の Span
がString.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);
old
とtan
の開始インデックスを知るための強力な方法はありますか?
'old'を検索するだけでは、 'cold'の 'old'が返されるため、機能しないことに注意してください。
willが機能するのは、事前に%[0-9]$s
を検索し、String.format
の置換を考慮してオフセットを計算することだと思います。 。これは頭痛の種のようですが、フォーマットの詳細についてより有益なString.format
のようなメソッドがあるのではないかと思います。さて、ありますか?
そのような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;
}
}
}
スパナブルで動作する String.format
のバージョンを作成しました。ダウンロードして、通常のバージョンと同じように使用してください。あなたの場合、フォーマット指定子の周りにスパンを配置します(おそらくstrings.xmlを使用します)。出力では、それらはそれらの指定子が置き換えられたものの周りにあります。
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
}
同じ問題がポップアップしましたが、ここで本当に素晴らしい解決策を見つけました: Android:Spannable.setSpanをString.formatと組み合わせる方法?
@ george-steelが提供するソリューションを確認してください。彼はスパンを保持する String.formatのカスタムバージョン を作成しました。
使用例:
Spanned toDisplay = SpanFormatter.format(getText(R.string.foo), bar, baz, quux);