将Spannable与String.format()结合使用

时间:2014-01-05 17:41:30

标签: android spannablestring spannable

假设您有以下字符串:

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"

假设您希望以此字符串结尾,但对String.format替换的任何单词设置了特定的Span

例如,我们还希望执行以下操作:

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',因此无效。

工作的内容,我想是在事先搜索%[0-9]$s,并计算偏移量以考虑String.format中的替换项。这看起来很令人头疼,我怀疑可能有一种像String.format这样的方法,它可以提供有关格式化细节的更多信息。嗯,有吗?

5 个答案:

答案 0 :(得分:15)

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

答案 1 :(得分:12)

使用像这样的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,我们会从this question借用自定义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;
        }
    }
}

答案 2 :(得分:3)

我制作了简单的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
}

答案 3 :(得分:0)

我弹出了同样的问题,但是在这里找到了一个非常好的解决方案: Android: How to combine Spannable.setSpan with String.format?

查看@ george-steel提供的解决方案。他创建了custom version of String.format来保留跨度。

示例用法:

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

答案 4 :(得分:0)

我需要用一组Spannables替换String中的%s占位符,但找不到满足要求的东西,因此我将自己的格式化程序实现为kotlin String扩展。希望它能对任何人有帮助。

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)