3

Original String:

Lorem ##ipsum## dolar ##sit## atem. Lorem ipsum dolar sit ##atem##.

After formating:

Lorem #ipsum dolar #sit atem. Lorem ipsum dolar sit #atem.

But only the last one has the Formating i want. See image below.

CODE

private void format() {
    CharSequence text = editContent.getText();

    MovementMethod movementMethod = editContent.getMovementMethod();
    if ((movementMethod == null) || !(movementMethod instanceof LinkMovementMethod))
    {
        editContent.setMovementMethod(LinkMovementMethod.getInstance());
    }

    text = setSpanBetweenTokens(text, "##", new ForegroundColorSpan(0xFF0099FF), new UnderlineSpan(), new ClickableSpan() {
        @Override
        public void onClick(View widget) {
            Toast.makeText(getApplicationContext(), "click", Toast.LENGTH_SHORT).show();
        }
    });

    editContent.setText(text);
}

private static CharSequence setSpanBetweenTokens(CharSequence text, String token, CharacterStyle... characterStyle) {
    int tokenLen = token.length();
    int start = text.toString().indexOf(token) + 1;
    int end = text.toString().indexOf(token, start);

    while (start > -1 && end > -1)
    {
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);
        for (CharacterStyle c : characterStyle) {
            spannableStringBuilder.setSpan(c, start, end, 0);
        }

        spannableStringBuilder.delete(end, end + tokenLen);
        spannableStringBuilder.delete(start - 1, start);

        text = spannableStringBuilder;

        start = text.toString().indexOf(token) + 1;
        end = text.toString().indexOf(token, start);
    }

    return text;
}

EDIT

My final Solution

private void format() {
    CharSequence text = editContent.getText();

    MovementMethod movementMethod = editContent.getMovementMethod();
    if ((movementMethod == null) || !(movementMethod instanceof LinkMovementMethod))
    {
        editContent.setMovementMethod(LinkMovementMethod.getInstance());
    }

    text = setSpanBetweenTokens(text, "##");

    editContent.setText(text);
}

private static CharSequence setSpanBetweenTokens(CharSequence text, String token) {
    int tokenLen = token.length();
    int start = text.toString().indexOf(token) + 1;
    int end = text.toString().indexOf(token, start);

    while (start > -1 && end > -1)
    {
        SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(text);

        spannableStringBuilder.setSpan(new ForegroundColorSpan(0xFF0099FF), start, end, 0);
        spannableStringBuilder.setSpan(new UnderlineSpan(), start, end, 0);
        spannableStringBuilder.setSpan(new ClickableSpan() {
            @Override
            public void onClick(View widget) {
                Log.d("DEBUG", "Click");
            }
        }, start, end, 0);

        spannableStringBuilder.delete(end, end + tokenLen);
        spannableStringBuilder.delete(start - 1, start);

        text = spannableStringBuilder;

        start = text.toString().indexOf(token) + 1;
        end = text.toString().indexOf(token, start);
    }

    return text;
}
CONDEVX
  • 485
  • 1
  • 8
  • 22

3 Answers3

4

Pass different object for each span:

spannableStringBuilder.setSpan(c, start, end, 0);

You're passing the same object for each span:

new ForegroundColorSpan(0xFF0099FF)

When span object exists in spannableStringBuilder then it changes bounds only, not a new span is added.

pawelzieba
  • 16,082
  • 3
  • 46
  • 72
  • Hey guys @pawelzieba i know this is really old but i tried the above code, and it still format only the one at the end `for (CharacterStyle c : cs) ssb.setSpan(c, start, end, 0);` – XY-JOE Apr 22 '18 at 15:55
3

I would suggest a simpler way. If your formatting needs are basic, a simple regex + Html.fromHtml() should do the trick:

private void format() {
    String mText = editContent.getText();

    Spanned mSpannedText = Html.fromHtml(mText.replaceAll("##(.*?)##)","<font color=\"0xFF0099\">#$1</font>"), 

    editContent.setText(mSpannedText);
}
Sébastien
  • 13,831
  • 10
  • 55
  • 70
  • I think both REGEX and WEBVIEWS are the last resource, they are both resource intensive and thus not good for something simple. – Jorge Aguilar Dec 26 '13 at 22:09
  • @Jorge Aguilar, I don't think this snippet creates a webview. The performance hit is negligible. – Sébastien Dec 27 '13 at 09:07
  • @Sebastien you right that snippet does not create a webview, but you still are using HTML to handle the problem and this brings parsing and other processing that is not needed, and you might be right that the performance hit is not that big on a small case, but if you try it on a bigger example the difference will start showing. And anyways, not because it can be done "fast enough" means you can use the resources as you wish, optimizing is always a good thing. – Jorge Aguilar Dec 27 '13 at 15:43
1

The final solution correctly loops however your first token will not be correctly deleted as you have used

int start = text.toString().indexOf(token) + 1;

which would only work if your token was 1 character in length. Since your chosen token is ## change the above code to utilise the already created variable tokenLen

int start = text.toString().indexOf(token) + tokenLen;

this will ensure your text is correctly edited and all trace of your tokens are removed.

Aldo
  • 55
  • 9