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.
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.
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
Detecting Sms Incoming and Outgoing
How to Change Default Images of Checkbox
Android.Content.Context.Getpackagename()' on a Null Object Reference
Loading Youtube Video Through Iframe in Android Webview
Why Is My Smallicon for Notifications Always Greyed Out
Using Picasso with Custom Disk Cache
Connecting 2 Emulator Instances in Android
Android: How to Get a Radiogroup with Togglebuttons
Android: Using Webview Outside an Activity Context
How to Stop Asynctask Thread in Android
Action_Sendto for Sending an Email
Read & Writing Arrays of Parcelable Objects
Android Runtimeexception: Unable to Instantiate the Service
How to Make an Android Slidingdrawer Slide Out from the Left
Android Studio Adb Syntax Error: ")" Unexpected
Save Image to Sdcard from Drawable Resource on Android
How to Turn Speaker On/Off Programmatically in Android 4.0
How to Automatically Add Thousand Separators as Number Is Input in Edittext