31

假设您有以下字符串:

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

例如,我们还想做以下事情:

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',因此这是行不通的。

我猜,什么%[0-9]$s起作用的是预先搜索,并计算偏移量以解释String.format. 不过,这似乎令人头疼,我怀疑可能有一种类似的方法可以提供String.format更多有关其格式细节的信息。嗯,有吗?

4

5 回答 5

17

我已经创建了一个适用于 spannables的版本。String.format下载它并像普通版本一样使用它。在您的情况下,您可以将跨度放在格式说明符周围(可能使用 strings.xml)。在输出中,它们将围绕这些说明符被替换的任何内容。

于 2014-11-30T07:13:31.227 回答
15

像这样使用 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;
        }
    }
}
于 2014-01-05T18:35:31.907 回答
5

我制作了简单的 Kotlin 扩展函数,应该可以解决String.format跨度问题:

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
}
于 2019-06-14T13:25:31.253 回答
2

我需要%s用一组 Spannables 替换字符串中的占位符,但没有找到任何令人满意的东西,所以我将自己的格式化程序实现为 kotlin 字符串扩展。希望它可以帮助任何人。

fun String.formatSpannable(vararg spans: CharSequence?): Spannable {
    val result = SpannableStringBuilder()
    when {
        spans.size != this.split("%s").size - 1 ->
            Log.e("formatSpannable",
                    "cannot format '$this' with ${spans.size} arguments")
        !this.contains("%s") -> result.append(this)
        else -> {
            var str = this
            var spanIndex = 0
            while (str.contains("%s")) {
                val preStr = str.substring(0, str.indexOf("%s"))
                result.append(preStr)
                result.append(spans[spanIndex] ?: "")
                str = str.substring(str.indexOf("%s") + 2)
                spanIndex++
            }
            if (str.isNotEmpty()) {
                result.append(str)
            }
        }
    }
    return result
}

然后用法如下

"hello %s kotlin %s world".formatSpannable(span0, span1)
于 2020-04-28T06:56:09.420 回答
0

我遇到了同样的问题,但在这里找到了一个非常好的解决方案: Android:如何将 Spannable.setSpan 与 String.format 结合起来?

查看@george-steel 提供的解决方案。他创建了保留跨度的 String.format 的自定义版本。

示例使用:

Spanned toDisplay = SpanFormatter.format(getText(R.string.foo), bar, baz, quux);
于 2020-03-17T13:17:29.763 回答