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

Maa*_*ten 23 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"
Run Code Online (Sandbox Code Playgroud)

假设您希望以此字符串结束,但也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);
Run Code Online (Sandbox Code Playgroud)

是否有结识的开始指数的稳健的方式oldtan

请注意,只搜索'old'会在'cold'中返回'old',因此无效.

什么的工作,我想,正在寻找%[0-9]$s事前,并计算补偿值以反映在更换String.format.这看起来很令人头疼,我怀疑可能有一种方法String.format可以提供有关其格式化细节的更多信息.好吧,有吗?

Geo*_*eel 15

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

  • @SnapDragon我刚用一个修复程序推送了一个新提交.谢谢你找到了这个bug. (2认同)

Maa*_*ten 13

使用像这样的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);
Run Code Online (Sandbox Code Playgroud)

问题:这没有放入StrikethroughSpan.为了做到这一点StrikethroughSpan,我们TagHandler这个问题中借用了一个习惯.

Spannable spannable = Html.fromHtml(text,null,new MyHtmlTagHandler());
Run Code Online (Sandbox Code Playgroud)

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 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
}
Run Code Online (Sandbox Code Playgroud)

  • 不确定这个解决方案是否正确,因为如果您的参数中有多个可跨越的对象,例如具有不同颜色但具有相同文本的对象,那么它们的顺序可能不正确,因为您正在寻找要由可跨越对象替换的文本内容。此外,如果在字符串中您有一个与可扩展文本之一内容相同的子字符串,则可能会出现另一种边缘情况,那么您的函数将在错误的位置替换。请参阅下面我的解决方案之一,以获得我认为更好的方向。 (2认同)