Android Spanned, Spannedstring, Spannable, Spannablestring and Charsequence

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.

How to loop through the spans in a SpannedString or SpannableString in Android

Looping through the spans in order

You can use getSpans to get an array of the spans in a Spanned or Spannable String. However, just looping through the getSpans results will not necessarily give them to you in order. To get them in order you can use nextSpanTransition.

Here is an example with a SpannedString like the example in the question. (A SpannableString would work the same.) The green lines show where the span transitions are. The text is black by default.

spannable string example

The code finds the next span transition and then gets all the spans in the current range.

int next;
for (int i = 0; i < spannableString.length(); i = next) {

// find the next span transition
next = spannableString.nextSpanTransition(i, spannedString.length(), CharacterStyle.class);

// get all spans in this range
int numOfSpans = 0;
CharacterStyle[] spans = spannableString.getSpans(i, next, CharacterStyle.class);
for(int j = 0; j < spans.length; j++) {
numOfSpans++;
}

Log.i("TAG", "spans from " + i + " to " + next + ": " + numOfSpans);
}

Output:

spans from 0 to 1: 0
spans from 1 to 3: 1
spans from 3 to 8: 2
spans from 8 to 11: 1
spans from 11 to 12: 0

Thanks to this code for ideas.

Types of spans

Normally when looping through the spans you would choose a certain type of span. For example, if you wanted to remove all the foreground color spans, you could do the following:

// get spans
ForegroundColorSpan[] spans = spannableString.getSpans(0, spannableString.length(), ForegroundColorSpan.class);

// loop through spans
for (ForegroundColorSpan span : spans) {
spannableString.removeSpan(span);
}

Note that this wouldn't work with a SpannedString because the spans in a SpannedString are not mutable (see this answer).

If you wanted to get all the spans of any type you would set the type as Object.class.

Object[] spans = spannableString.getSpans(0, spannableString.length(), Object.class);

If you wanted all the spans that affect the appearance at the character level, you would use CharacterStyle.class. If within the loop you wanted to further limit the spans to those belonging to MetricAffectingSpan, you could do it like this.

CharacterStyle[] spans = spannableString.getSpans(0, spannableString.length(), CharacterStyle.class);
for (CharacterStyle span : spans) {
if (span instanceof MetricAffectingSpan) {
// do something
}
}

Here is a general hierarchical breakdown of the span types. It may not be complete. Read Spans, a Powerful Concept for more information.

Object
CharacterStyle
BackgroundColorSpan
ClickableSpan
URLSpan
ForegroundColorSpan
MaskFilterSpan
StrikethroughSpan
SuggestionSpan
UnderlineSpan
MetricAffectingSpan
AbsoluteSizeSpan
LocaleSpan
RelativeSizeSpan
ReplacementSpan
DynamicDrawableSpan
ImageSpan
ScaleXSpan
StyleSpan
SubscriptSpan
SuperscriptSpan
TextAppearanceSpan
TypefaceSpan
ParagraphStyle
AlignmentSpan
AlignmentSpan.Standard
BulletSpan
DrawableMarginSpan
IconMarginSpan
LeadingMarginSpan
LeadingMarginSpan.LeadingMarginSpan2
LeadingMarginSpan.Standard
LineBackgroundSpan
LineHeightSpan
LineHeightSpan.WithDensity
QuoteSpan
TabStopSpan
TabStopSpan.Standard
WrapTogetherSpan

Best way to get a SpannableString from a SpannableStringBuilder

There is no need to convert a SpannableStringBuilder to a SpannableString when you can use it directly, as in a TextView:

SpannableStringBuilder stuff = complex_routine_that_builds_it();
textView.setText(stuff);

As the question already showed, you could make the conversion if you absolutely had to by

SpannableStringBuilder stuff = complex_routine_that_builds_it();
SpannableString result = SpannableString.valueOf(stuff);

but you should consider why you would even need to make that conversion? Just use the original SpannableStringBuilder.

SpannableString vs SpannableStringBuilder

The difference between these two is similar to the difference between String and StringBuilder. A String is immutable but you can change the text in a StringBuilder.

Similarly, the text in a SpannableString is immutable while the text in a SpannableStringBuilder is changeable. It is important to note, though, that this only applies to the text. The spans on both of them (even a SpannableString) can be changed.

Related

  • Android Spanned, SpannedString, Spannable, SpannableString and CharSequence

Improving spanned/spannable performance in setting TextView RecylcerView

In java you should just override method newSpannable, in its implementation cast source (CharSequence) to Spannable and set this factory to TextView (tvText in my case)

       tvText.setSpannableFactory(new Spannable.Factory(){
@Override
public Spannable newSpannable(CharSequence source) {
return (Spannable) source;
}
});

Keep in mind, it should be set from ViewHolder's contructor, not onBindViewHolder. When you get reference to it by findViewById.

How to find index of a substring in a SpannableString

TextUtils has many variations of indexOf() to find the index of CharSequence in another CharSequence. Both String and SpannedString are CharSequence implementations.

Delete characters from spannable CharSequence

This sounded like a fun thing to try to figure out.

The key was SpannableStringBuilder. With SpannableString the text itself is immutable, but with SpannableStringBuilder both the text and the markup can be changed. With that in mind I modified your snippet a little to do what you want:

public static CharSequence colorBackground(CharSequence text) {

Pattern pattern = Pattern.compile("#(.*?)#");

SpannableStringBuilder ssb = new SpannableStringBuilder( text );

if( pattern != null )
{
Matcher matcher = pattern.matcher( text );
int matchesSoFar = 0;
while( matcher.find() )
{
int start = matcher.start() - (matchesSoFar * 2);
int end = matcher.end() - (matchesSoFar * 2);
CharacterStyle span = new BackgroundColorSpan(0xFF404040);
ssb.setSpan( span, start + 1, end - 1, 0 );
ssb.delete(start, start + 1);
ssb.delete(end - 2, end -1);
matchesSoFar++;
}
}
return ssb;
}

I don't have much experience with Spannables in general, and I don't know if the way I've gone about deleting the "#" is the best method, but it seems to work.

Android: converting between Strings, SpannedStrings and Spannablestrings

It appears there isn't a more straightforward way to accomplish this.

Casting between SpannableStringBuilder and CharSequence

1. Well the reason (SpannableStringBuilder ssb_2 = (SpannableStringBuilder)cs;) doesn't work is because SpannableStringBuilder implements CharSequence, meaning CharSequence does not know it can be casted to that. You can however do it the other way around. Does that make sense?

CharSequence is a nothing/itself

SpannableStringBuilder is a: CharSequence, Spannable, Editable, etc...

2. As for why android:bufferType="spannable" works, you are doing what I said above, the opposite. Since SpannableString implements CharSequence, it is now a child of it and therefore can be placed in CharSequence.

But anyways, the correct way to put your CharSequence in SpannableStringBuilder is by doing:

SpannableStringBuilder ssb_2 = SpannableStringBuilder(cs);

You might want to brush up on some polymorphism :) but you can see this in the Android docs on SpannableStringBuilder's constructor, or one of them at least.


Update:

From what I notice on what you are doing, what is the need to use CharSequence? Just leave the TextView as is, meaning its returned as a String. So doing something like this would be easier:

SpannableStringBuilder ssb_2 = SpannableStringBuilder(tv.getText());

Reason that works is because String implements CharSequence as well, meaning that it as well can be passed into the SpannableStringBuilder constructor as a CharSequence. Java does automatic casting in certain cases, including in that code right above.

How to merge some spannable objects?

You could use this:

TextUtils.concat(span1, span2);

http://developer.android.com/reference/android/text/TextUtils.html#concat(java.lang.CharSequence...)



Related Topics



Leave a reply



Submit