Android EditText StyleSpan Behavior

I recently configured an Android EditText to mimic Simplenote's iOS note UI – the first line of text is bold, and anything after that is normal. I had a simple static method to split a string at the first line break:

public static String[] splitAtFirstLineBreak(String string) {
   return string.split("\n", 2);
}

and I implemented the TextWatcher interface so I could apply the styling when the user added or removed text. The only method you need is afterTextChanged, in which you get an Editable text object that you can modify.

Styles Persist to New Input

At first, I thought I would just apply the bold style to the text before the newline, and leave the rest of the text as-is. I figured that any text I didn't specify a style on would be unstyled. This is actually not the case.

It turns out that EditText automatically applies the style preceding the cursor to any input. If you think about writing a document, this makes sense: if you place your cursor at the end of some bold text and start typing, most editors will continue to apply the bold style to your new input. In the case of an EditText, this means incrementing the end index of any applicable StyleSpans.

Style All the Text, Every Time

Knowing this, we can amend our solution: first, remove any styling we want control over, and then re-apply it only to exact places we want it. I ended up with something like this (mine is a little more abstracted, but I put it in a single method here for ease of comprehension):

public class BoldTitleStylingTextWatcher implements TextWatcher {

    private boolean isFormatting = false;

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

    }

    @Override
    public void afterTextChanged(Editable editable) {
        if (isFormatting) {
            return;
        }

        isFormatting = true;

        String text = editable.toString();

        for (StyleSpan s : editable.getSpans(0, editable.length(), StyleSpan.class)) {
            editable.removeSpan(s);
        }
        
        String textToBold = Strings.splitAtFirstLineBreak(text)[0];
        StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
        int start = text.indexOf(textToBold);
        int end = start + textToBold.length();
        editable.setSpan(styleSpan, start, end, Spannable.SPAN_INCLUSIVE_INCLUSIVE);

        isFormatting = false;
    }
}

You could apply this knowledge for more advanced styling as well, perhaps to move or modify existing styles, or to disable this style-extending behavior. It's an all-around useful thing to know when dealing with EditText styling, and something to consider if you ever make your own styled text input.