Подтвердить что ты не робот

Объединение 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"

Предположим, вы хотите закончить эту строку, но также можете указать 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?

Обратите внимание, что только поиск "старого" возвращает "старый" в "холодном", так что это не сработает.

Что будет работать, я думаю, заранее ищет %[0-9]$s и вычисляет смещения для учета замен в String.format. Это похоже на головную боль, хотя я подозреваю, что может быть такой метод, как String.format, который более информативен в отношении особенностей его форматирования. Ну, есть?

4b9b3361

Ответ 1

Использование 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;
        }
    }
}

Ответ 2

Я создал версию String.format, которая работает с spannables. Загрузите его и используйте его как обычную версию. В вашем случае вы поместите пробелы вокруг спецификаторов формата (возможно, используя strings.xml). На выходе они будут вокруг того, что заменили эти спецификаторы.

Ответ 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
}