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
SPAN_INCLUSIVE_EXCLUSIVE
SPAN_EXCLUSIVE_INCLUSIVE
SPAN_EXCLUSIVE_EXCLUSIVE
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
SPAN_INCLUSIVE_EXCLUSIVE
SPAN_EXCLUSIVE_INCLUSIVE
SPAN_EXCLUSIVE_EXCLUSIVE
Notes
- In the second example I had to use a
SpannableStringBuilder
because the text in aSpannableString
is immutable so you can't insert text into it. Thus, the flags are generally meaningless for aSpannableString
. However, one could imagine a situation in which the spans from aSpannableString
get copied over to aSpannableStringBuilder
orEditable
, 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
In order change the spans, you need to do the following things
- Get all the spans of the desired type by using
getSpans()
- Find the range of each span with
getSpanStart()
andgetSpanEnd()
- Remove the original spans with
removeSpan()
- 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 theSpannableString
. You would use the originalboldString
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?
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
).
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
What Is a Maximum Size of SQLite Database on Android
Listen to Own Application Uninstall Event on Android
How to Disable Status Bar Click and Pull Down in Android
Using Camera in the Android Emulator
Getactionbar() Returns Null (Appcompat-V7 21)
Apache Http Connection with Android 6.0 (Marshmallow)
Get File Path of Image on Android
Pagination Not Work for the Recyclerview Within Nestedscrollview
Continuous Speech Recognition Android
How to Set Alarm in Android Programmatically
Android Bluetoothdevice.Getname() Return Null
Android Saf (Storage Access Framework): Get Particular File Uri from Treeuri
Image Share Intent Works for Gmail But Crashes Fb and Twitter
Seekbar and Media Player in Android
How to Finish Current Activity in Android
Unable to Execute Adb in Ubuntu. Downloaded File Is Meant for X86-64 While I Have I686