Explain the Meaning of Span Flags Like Span_Exclusive_Exclusive

Explain the meaning of Span flags like SPAN_EXCLUSIVE_EXCLUSIVE

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

Sample Image

SPAN_INCLUSIVE_EXCLUSIVE

Sample Image

SPAN_EXCLUSIVE_INCLUSIVE

Sample Image

SPAN_EXCLUSIVE_EXCLUSIVE

Sample Image

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

Sample Image

SPAN_INCLUSIVE_EXCLUSIVE

Sample Image

SPAN_EXCLUSIVE_INCLUSIVE

Sample Image

SPAN_EXCLUSIVE_EXCLUSIVE

Sample Image

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.

Explain the definitions of these flags (SPAN_COMPOSING, SPAN_USER, etc.) from the Spanned interface

SPAN_COMPOSING is a flag used on text being input, and is considered a temporary span, intended to be removed once input is completed, "This flag is set on spans that are being used to apply temporary styling information on the composing text of an input method, so that they can be found and removed when the composing text is being replaced."

Here is an example of code which uses SPAN_COMPOSING and removes it from the text; relevant code is quoted below:

public static final void removeComposingSpans(Spannable text) {

text.removeSpan(COMPOSING);
Object[] sps = text.getSpans(0, text.length(), Object.class);

if (sps != null) {

for (int i = sps.length-1; i >= 0; i--) {

Object o = sps[i];

if ((text.getSpanFlags(o) & Spanned.SPAN_COMPOSING) != 0) {

text.removeSpan(o);
}
}
}
}

public static void setComposingSpans(Spannable text) {

final Object[] sps = text.getSpans(0, text.length(), Object.class);

if (sps != null) {

for (int i = sps.length - 1; i >= 0; i--) {

final Object o = sps[i];

if (o == COMPOSING) {

text.removeSpan(o);
continue;
}

final int fl = text.getSpanFlags(o);

if ((fl & (Spanned.SPAN_COMPOSING | Spanned.SPAN_POINT_MARK_MASK))
!= (Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) {

text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o),
(fl & Spanned.SPAN_POINT_MARK_MASK)
| Spanned.SPAN_COMPOSING
| Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}

text.setSpan(COMPOSING, 0, text.length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING);
}

SPAN_INTERMEDIATE is to be treated as a flag used to assign to a span temporarily while it is undergoing change, and to be removed after the change is applied. "This flag will be set for intermediate span changes, meaning there is guaranteed to be another change following it."

SPAN_POINT_MARK_MASK is a bitmask, primarily used for comparison purposes to check if a qualifying bit state was applied. See proper use for it in the code quoted above.

SPAN_PRIORITY refers to the priority of the text layout for update purposes; the API notes that it should only be set during extraordinary circumstances and is therefore not necessary to be set by the developer.

SPAN_USER and SPAN_USER_SHIFT are storage areas for additional custom scalar data to be stored with the span if the developer chooses to use them.

What is the difference between SPAN_POINT_MARK and SPAN_MARK_POINT?

The way I like to explain MARK vs POINT is by representing them as square brackets at whatever offset they exist in a range of text. The direction the bracket is pointing shows the character that the mark or point is "attached" to.

So for a POINT, you would use the open bracket - it's attached to the character following it. And for a MARK, you would use the close bracket - it's attached to the character preceding it.

Let's look at some examples of each type:

SPAN_MARK_MARK

Inserting at the offset of a 0-length span: The marks remain fixed.


Before: Lorem ]]ipsum dolor sit.
After: Lorem ]]INSERTipsum dolor sit.

Inserting at the start of a non-0-length span: The inserted text is included in the range of the span.


Before: Lorem ]ipsum] dolor sit.
After: Lorem ]INSERTipsum] dolor sit.

Inserting at the end of a non-0-length span: The inserted text is excluded from the range of the span.


Before: Lorem ]ipsum] dolor sit.
After: Lorem ]ipsum]INSERT dolor sit.

You can see from the last two examples, why the SPAN_MARK_MARK flag is synonymous with the SPAN_INCLUSIVE_EXCLUSIVE flag. Text inserted at the start of the span is included in the range, while text inserted at the end is excluded.

SPAN_POINT_POINT

Inserting at the offset of a 0-length span: The points are pushed forward.


Before: Lorem [[ipsum dolor sit.
After: Lorem INSERT[[ipsum dolor sit.

Inserting at the start of a non-0-length span: The inserted text is excluded from the range of the span.


Before: Lorem [ipsum[ dolor sit.
After: Lorem INSERT[ipsum[ dolor sit.

Inserting at the end of a non-0-length span: The inserted text is included in the range of the span.


Before: Lorem [ipsum[ dolor sit.
After: Lorem [ipsumINSERT[ dolor sit.

Again you can see from the last two examples why the SPAN_POINT_POINT flag is synonymous with the SPAN_EXCLUSIVE_INCLUSIVE flag. Text inserted at the start of the span is excluded from the range, while text inserted at the end is included.

SPAN_MARK_POINT

Inserting at the start of the span: The inserted text is included in the range.


Before: Lorem ]ipsum[ dolor sit.
After: Lorem ]INSERTipsum[ dolor sit.

Inserting at the end of the span: The inserted text is still included in the range.


Before: Lorem ]ipsum[ dolor sit.
After: Lorem ]ipsumINSERT[ dolor sit.

And thus it has the synonym SPAN_INCLUSIVE_INCLUSIVE - the inserted text is always included in the range of the span.

SPAN_POINT_MARK

Inserting at the start of the span: The inserted text is excluded from the range.


Before: Lorem [ipsum] dolor sit.
After: Lorem INSERT[ipsum] dolor sit.

Inserting at the end of the span: The inserted text is still excluded from the range.


Before: Lorem [ipsum] dolor sit.
After: Lorem [ipsum]INSERT dolor sit.

And thus it has the synonym SPAN_EXCLUSIVE_EXCLUSIVE - the inserted text is always excluded from the range of the span.

I think the documentation confuses things by splitting the definitions of some of the synonyms. For example, when describing SPAN_MARK_MARK, they only define its usage in terms of a 0-length span. Then when defining SPAN_INCLUSIVE_EXCLUSIVE (which is a synonym) they only define its usage in terms of non-0-length spans. I think it would have been a lot clearer if they stated up front that they were synonyms, and had a single definition shared by both terms.

How to change one span type to another in Android?

How to change spans from one type to another

Sample Image

In order change the spans, you need to do the following things

  1. Get all the spans of the desired type by using getSpans()
  2. Find the range of each span with getSpanStart() and getSpanEnd()
  3. Remove the original spans with removeSpan()
  4. Add the new span type with setSpan() in the same locations as the old spans

Here is the code to do that:

Spanned boldString = Html.fromHtml("Some <b>text</b> with <b>spans</b> in it.");

// make a spannable copy so that we can change the spans (Spanned is immutable)
SpannableString spannableString = new SpannableString(boldString);

// get all the spans of type StyleSpan since bold is StyleSpan(Typeface.BOLD)
StyleSpan[] boldSpans = spannableString.getSpans(0, spannableString.length(), StyleSpan.class);

// loop through each bold span one at a time
for (StyleSpan boldSpan : boldSpans) {

// get the span range
int start = spannableString.getSpanStart(boldSpan);
int end = spannableString.getSpanEnd(boldSpan);

// remove the bold span
spannableString.removeSpan(boldSpan);

// add an underline span in the same place
UnderlineSpan underlineSpan = new UnderlineSpan();
spannableString.setSpan(underlineSpan, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}

Notes

  • If you want to just clear all the old spans, then use boldString.toString() when creating the SpannableString. You would use the original boldString to get the span ranges.

See also

  • Is it possible to have multiple styles inside a TextView?
  • Looping through spans in order (explains types of spans)
  • Meaning of Span flags

SPAN_EXCLUSIVE_EXCLUSIVE spans cannot have a zero length

Just add this property to your edit text and your problem will definitely be resolved

android:inputType="textNoSuggestions"

Add the above property to each of your edit text used in the application, or simply where you get the error in that edit text only.

Android Spanned, SpannedString, Spannable, SpannableString and CharSequence

What is the purpose of these interfaces?

diagram from yuml.me/edit/5f8da6cb
CharSequence is a standard Java interface representing a sequence of characters. String is the most commonly-used concrete implementation of CharSequence, followed by StringBuilder.

Spanned is a CharSequence with "spans" indicating formatting to apply to portions of the text, where those spans cannot be modified.

Spannable is a Spanned, adding in the ability to modify the spans (to add or remove formatting), but not to modify the text itself.

SpannedString is a concrete implementation of the Spanned interface.

SpannableString is a concrete implementation of the Spannable interface.

in which scenarios is it mostly common to use them?

When there is a method that returns one (e.g., getText() on an EditText) or when there is a method that takes one as a parameter (e.g., setText() on a TextView).
diagram from yuml.me/edit/35c8f39f

Your cited case of using Html.fromHtml() is perhaps the most common in conventional Android development, as a TextView with a Spanned is much lighter in weight than is a WebView. However, there are other use cases, such as:

  • Highlighting search results

  • Allowing users to enter in rich text, then using Html.toHtml() to persist that formatted text in an HTML rendition

In which cases is it best to avoid using them?

They are singularly awful at combating baldness, snow removal, heat pump repair, making a soufflé, etc.

:-)

Are there any obvious performance impacts to be considered when using any one of them?

Interfaces, by definition, do not have "performance impacts" -- they are merely a description of an API.

I am not aware that SpannableString is significantly slower than SpannedString at any particular operation. However, SpannableStringBuilder (which allows for manipulating the text in addition to the spans that format that text) may well be a bit slower than SpannableString or SpannedString for various things. Whether or not the performance differences are enough to matter will depend on usage, though.



Related Topics



Leave a reply



Submit