Explain the meaning of Span flags like SPAN_EXCLUSIVE_EXCLUSIVE

Can someone clearly explain with examples what each of the span flags like SPAN_EXCLUSIVE_EXCLUSIVE and SPAN_MARK_MARK mean and when to use what flags?

I do not understand the official documentation when it says:

Spans of type SPAN_EXCLUSIVE_EXCLUSIVE do not expand to include text inserted at either their starting or ending point.

Does "expand to include" refer to edits made after inserting the spans?

Does it mean that these flags do NOT affect Spannables with immutable text?


What the flags don't mean

When I first saw the INCLUSIVE and EXCLUSIVE parts of the Spannable flags, I thought they just told whether or not the span included the start and end index positions of the span. This is not true. Let me illustrate that with the following example.

String myString = "01234";
int start = 1;
int end = 3;
int spanFlag = Spannable.SPAN_INCLUSIVE_INCLUSIVE; // this is what is changing

SpannableString spannableString = new SpannableString(myString);
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
spannableString.setSpan(foregroundSpan, start, end, spanFlag);
textView.setText(spannableString);

Here are the results:

SPAN_INCLUSIVE_INCLUSIVE

enter image description here

SPAN_INCLUSIVE_EXCLUSIVE

enter image description here

SPAN_EXCLUSIVE_INCLUSIVE

enter image description here

SPAN_EXCLUSIVE_EXCLUSIVE

enter image description here

They are all the same! The flags don't affect the span. A span always includes the character at its start index and excludes the character at the end index.

What the flags really mean

The INCLUSIVE and EXCLUSIVE parts of the Spannable flags actually tell whether or not the span should include text that is inserted at the start or end positions.

Here is a modified example to illustrate that.

String myString = "01234";
int start = 1;
int end = 3;
int spanFlag = Spannable.SPAN_INCLUSIVE_INCLUSIVE; // this is what is changing

// set the span
SpannableStringBuilder spannableString = new SpannableStringBuilder(myString);
ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
spannableString.setSpan(foregroundSpan, start, end, spanFlag);

// insert the text after the span has already been set
// (inserting at start index second so that end index doesn't get messed up)
spannableString.insert(end, "x");
spannableString.insert(start, "x");

textView.setText(spannableString);

Here are the results after inserting an x at the end and start indexes:

SPAN_INCLUSIVE_INCLUSIVE

enter image description here

SPAN_INCLUSIVE_EXCLUSIVE

enter image description here

SPAN_EXCLUSIVE_INCLUSIVE

enter image description here

SPAN_EXCLUSIVE_EXCLUSIVE

enter image description here

Notes

  • In the second example I had to use a SpannableStringBuilder because the text in a SpannableString is immutable so you can't insert text into it. Thus, the flags are generally meaningless for a SpannableString. However, one could imagine a situation in which the spans from a SpannableString get copied over to a SpannableStringBuilder or Editable, and from there the flags would have meaning.
  • See this answer for the difference between SpannableString, SpannableStringBuilder, Editable, and more.

Does "expand to include" refer to edits made after inserting the spans?

Yes. For example, let's suppose we have the following:

The quick fox jumped.

If we used SPAN_EXCLUSIVE_EXCLUSIVE on the boldface span, and we insert text in the middle of the span, it is still boldface:

The quick brown fox jumped.

However, if we insert text at the beginning or the end of the boldface span, the inserted text is not boldface:

The really quick fox jumped.

If, however, we had used SPAN_INCLUSIVE_EXCLUSIVE, then inserting text at the beginning would be included as part of the span, and we would have:

The really quick fox jumped.

Does it mean that these flags do NOT affect Spannables with immutable text?

I would say that they have limited use for immutable text. Mostly, these will be used with SpannableStringBuilder or things that use one under the covers (e.g., EditText).


The String values we keep as a resource within a xml file in the res/values directory can be styled with the methods defined in the android.text.Spannable interface,the fields defined in the android.text.Spanned interface (Spannable is a subinterface of Spanned), with the android.text.SpannableStringBuilder class (SpannableStringBuilder is a subclass of Spannable) and with the classes defined in the android.text.style package specially the android.text.style.ForegroundColorSpan and android.text.style.StyleSpan classes. Since SpannableStringBuilder is a subclass of Spannable and Spannable is a subinterface of Spanned.All the fields of Spanned interface and a complete implementation of the methods of the Spannable interface exists in the SpannableStringBuilder class.

1.Now to style a text resource we must get it first with its resource id with the method getString(int resource_id) method from a Resources object,to get a Resources object associated with a Context subclass we call the getResources() method.For example-

String title = getResources().getString(R.string.string_title);

2.Now we need to create a SpannableStringBuilder object with its public no parameter constructor or any other public constructor.

3.SpannableStringBuilder works in a little complex manner.We can append text to a SpannableStringBuilder with one of the append methods.Some part of the text a SpannableStringBuilder holds can be marked as spanned and only that part gets styled (with color,font etc.),when a new text is appended within a SpannableStringBuilder object at any index,if the new text will also be marked as spaned or not,that is defined by the fields of the Spanned interface. We call the setSpan(Object what, int start, int end, int flags) method declared by the Spannable interface and implemented by the SpannableStringBuilder class. Here what is a object of a class of the android.text.style.StyleSpan package,this object defines the type of the styling we want to apply,flags is one of the field defined in the Spanned interface,this flags parameter specifies when a new text is appended within this SpannableStringBuilder object at any index,if the new text will also be marked as spanned or not.

4.The different classes of the android.text.style.StyleSpan package defines different styling,but as of now I only know one usage of the android.text.style.StyleSpan class and one usage of the android.text.style.ForegroundColorSpan class. One public constructor of the StyleSpan class takes a int value which may be the value of one of the fields defined in the android.graphics.Typeface class ( BOLD,BOLD_ITALIC,ITALIC,NORMAL ).As these field name suggest they will style the text as bold or bold_italic or italic or normal. For example-

StyleSpan style_span=new StyleSpan(Typeface.ITALIC);

One public constructor of the ForegroundColorSpan class takes a int value which may be any hexadecimal argb color value or may be the value of one of the fields defined in the android.graphics.Color class ( BLACK,BLUE,CYAN,DKGRAY,GRAY,GREEN,LTGRAY,MAGENTA,RED,TRANSPARENT,WHITE,YELLOW ).As the names of these fields suggest they color a text.To give a argb hexadecimal color value as a int to constructor we start the hexadecimal value with a 0X or 0x,java then automatically converts the hexadecimal value into its int equivalent. For example-

ForegroundColorSpan fore_ground_color_Span = new ForegroundColorSpan(Color.RED);

5.In the setSpan(Object what, int start, int end, int flags) method, the start and the end defines the initial range of the text to be marked as spanned,start specifies the index of the starting characters and end specifies the index of the character one passed the ending character,for example- for a String "01234"

new SpannableStringBuilder("01234").setSpan(foreground_color_span, 1, 3, span_flag);

will cause only the 12 be marked as spanned initially,so starting character is the '1' as specified by the start index and the ending character is the 2 as specified by the index (end-1). So if we give the same value for both start and end,then there will be no text covered initially and the range will be zero,for the above example if start and end both are 1,then starting character is 1 but the ending character is 0.this makes no sense in this case there will be no text marked as spanned initially.

6.Now some fields of the Spanned interface work as described below. When a new text is appended.......... the SPAN_EXCLUSIVE_EXCLUSIVE does not mark any text appended before the starting character and after the ending character as spanned,but marks any text appended after the starting character or before the ending character as spanned, SPAN_EXCLUSIVE_EXCLUSIVE is the synonym for SPAN_POINT_MARK,if the range is zero that means no text is marked as spanned initially,then any text appended at any index is not marked as spanned with this flag,

the SPAN_EXCLUSIVE_INCLUSIVE does not mark any text appended before the starting character as spanned,but marks any text appended after the starting character or any text appended before or after the ending character as spanned,according to my tests SPAN_EXCLUSIVE_INCLUSIVE is the synonym for SPAN_POINT_POINT,if the range is zero that means no text is marked as spanned initially,then any text appended at any index is not marked as spanned with this flag,

the SPAN_INCLUSIVE_EXCLUSIVE does not mark any text appended before the ending character as spanned,but marks any text appended after or before the starting character or any text appedned after the ending character as spanned,according to my tests SPAN_INCLUSIVE_EXCLUSIVE is the synonym for SPAN_MARK_MARK,if the range is zero that means no text is marked as spanned initially,then any text appended at any index is not marked as spanned with this flag,

the SPAN_INCLUSIVE_INCLUSIVE marks any text appended after or before the starting and the ending character as spanned,SPAN_INCLUSIVE_INCLUSIVE is the synonym for SPAN_MARK_POINT,if the range is zero that means no text is marked as spanned initially,then any text appended at any index after or before the starting or ending character is always marked as spanned with this flag,

I have not been able understand the other flag constants defined in the Spanned interface properly.

7.Now we can append any text within our SpannableStringBuilder object with one of the append methods or one of the insert methods for example-

SpannableStringBuilder text = new SpannableStringBuilder(); ...... text.append(string_value); //append(CharSequence text) appends given String to the end //insert(int where, CharSequence tb) inserts the given String after the character whose index is (where-1)

The new text inserted to this SpannableStringBuilder will be marked as "spanned and styled" or not ,will depend on the flag we have specified to the previous call to the setSpan(Object what, int start, int end, int flags) method.

8.If we want to apply more than one styling to the text in a SpannableStringBuilder object,then we can call the setSpan(Object what, int start, int end, int flags) method again and specify the styling to the what parameter,and the start and end to be styled initially,then the flag parameter to specify how the styling should behave when any new text is inserted. Here a important thing is that when we call the span method,the styling we specify is paired with the start,end and flag which is independent of any previous call to the span method,that means each call to the span method applies a styling which works independently of any other styling which is also applied to the same text.Even if we apply the same styling again but with a new object to the "what" parameter,this styling also works independently of the previously applied same styling,only way to modify a already applied styling is to specify the same object to the "what" parameter. For example-

    TextView txv=(TextView)findViewById(R.id.textView9);
    String myString = "01234";
    Object ob;
    int start = 1;
    int end = 4;
    int spanFlag = Spannable.SPAN_INCLUSIVE_INCLUSIVE; 
    SpannableStringBuilder spannableString = new SpannableStringBuilder(myString);
    ForegroundColorSpan foregroundSpan = new ForegroundColorSpan(Color.RED);
    ob=foregroundSpan;
    spannableString.setSpan(foregroundSpan, start, end, spanFlag);
    txv.setText(spannableString);

This will result in txv has "01234",where the part "123" is red.

    end=3;
    spanFlag = Spannable.SPAN_EXCLUSIVE_INCLUSIVE;
    spannableString.setSpan(new StyleSpan(Typeface.BOLD), start, end, spanFlag);
    txv.setText(spannableString);

This will result in txv has "01234",where the part "123" is red,and the sub-part "12" is both bold (fatty) and red but "3" is only red not bold because of the "end" value.

    spannableString.insert(end, "x");
    spannableString.insert(start, "x");
    txv.setText(spannableString);

This will cause txv to have "0x12x34",where the part "x12x3" being red and only "12x3" being bold, this happened because the red styling was initially applied to the text "123" with the flag SPAN_INCLUSIVE_INCLUSIVE,that means any text appended after or before 1 and 3 are always included in the red styling region,hence "x12x3" being red the bold styling was initially applied to the text "12" with the flag SPAN_EXCLUSIVE_INCLUSIVE,that means any text appended before 1 is not included in the styling region but any text appended after 1 and after or before 2 is always included in the bold styling regio,hence only "12x3" being bold.

    int end=3;
    spanFlag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE; 
    foregroundSpan = new ForegroundColorSpan(Color.RED);
    spannableString.setSpan(foregroundSpan, start, end, spanFlag);
    txv.setText(spannableString);

This will cause another independent red styling on the text "x1",but since already a other red styling is applied to the text "x12x3",we will see no difference in txv. To actually change the previously applied red styling,we need to pass the same ForegroundColorSpan object as we did when we called the setSpan method on the first occasion,we should use the following code instead of the above,

    int end=3;
    spanFlag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE; 
    spannableString.setSpan(ob, start, end, spanFlag); //ob refers to the same the styling object as we specified on the first occasion
    txv.setText(spannableString);

This will cause only "x1" to be red and also if we append any text after "1" or before "x",then that text will not be included in the red styling region,but any text after "x" and before "1" is always included in the red styling region,because of the flag SPAN_EXCLUSIVE_EXCLUSIVE that we specified this time.

9.Last thing if we want to remove a styling and its span flag rules completely,then we can call the removeSpan(Object what) method defined by Spannable interface implemented by SpannableStringBuilder class,we need to pass the same styling object to the removeSpan method as we gave to the setSpan method.

Thanks to the above answers,specially the color example helped me a lot to understand these flags properly.