将 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"

假设您想以这个字符串结束,但也要为替换为 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的起始指数?

请注意,仅搜索"旧"会

返回"cold"中的"旧",因此这不起作用。

我想,有效的方法是事先搜索%[0-9]$s,并计算偏移量以解释String.format中的替换。这似乎是一个令人头疼的问题,我怀疑可能有一种方法像String.format可以更提供有关其格式细节的信息。嗯,有吗?

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

像这样使用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;
        }
    }
}

我做了一个简单的 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
}
我需要用

一组 Spannable 替换字符串中的%s占位符,并且没有找到任何足够令人满意的东西,所以我实现了我自己的格式化程序作为 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)

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

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

使用示例:

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

使用TextUtils.expandTemplate是最好的解决方案。无需使用第三方库、解析 HTML 或维护自己的代码。

例:

val formattedText = SpannableString.valueOf("world")
formattedText.setSpan(
  StyleSpan(Typeface.BOLD), 0, formattedText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
TextUtils.expandTemplate(
  "Hello ^1",
  formattedText
)

将创建字符串"你好世界"

相关内容

  • 没有找到相关文章

最新更新