Android Tutorial CharSequence / CSBuilder Tutorial

Status
Not open for further replies.

Erel

Administrator
Staff member
Licensed User
B4A v6.80 adds several new features related to the ability to format rich strings.



CharSequence is a native interface in Android SDK. A String is one implementation of CharSequence.
There are other implementations of CharSequence that provide more features and allow us to format the string, add images and even make parts of the text clickable.

Starting from B4A v6.80 many methods accept CharSequence instead of String. Existing code will work properly as you can pass regular strings. However you can now also pass more interesting CharSequences.

Note to library developers, if your library makes calls to APIs that work with CharSequences then you should change your method signatures to expect CharSequence instead of String. This will allow developers to format the text.

There are two ways to create CharSequences: agraham's RichString library or the new CSBuilder object.

This tutorial covers the new CSBuilder object.
CSBuilder is similar to StringBuilder. Instead of building strings, it builds CharSequences that include style information.

Using it is quite simple.
B4X:
Dim cs As CSBuilder
Label1.Text = cs.Initialize.Color(Colors.Red).Append("Hello World!").PopAll


Almost all methods of CSBuilder return the object itself. This allows us to chain the method calls.
Text is always appended with the Append method.
There are various attributes that can be set. Setting an attribute marks the beginning of a style span.
Calling Pop ends the last span that was added (and not ended yet).
Calling PopAll ends all open spans. It is convenient to always call PopAll at the end to ensure that all spans are closed.

B4X:
'example of explicitly popping an attribute:
Label1.Text = cs.Initialize.Color(Colors.Red).Append("Hello ").Pop.Append("World!").PopAll


B4X:
'It doesn't matter whether the methods are chained or split into several lines:
Dim cs As CSBuilder
cs.Initialize.Color(Colors.Red).Append("Hello ")
cs.Bold.Color(Colors.Green).Append("Colorful ").Pop.Pop 'two pops: the first removes the green color and the second removes the bold style
cs.Append("World!").PopAll
Label1.Text = cs
'can also be set as the activity title
Activity.Title = cs
'and Toast messages and in other places...
ToastMessageShow(cs, True)


Using the new Typeface.FONTAWESOME and MATERIALICONS

B4X:
Dim cs As CSBuilder
Label1.Text = cs.Initialize.Append("Text with FontAwesome: ").Typeface(Typeface.FONTAWESOME).Append(Chr(0xF209)).PopAll
'Using the same builder multiple times. Note that it is initialized each time.
'Note that we vertically align the material icon character.
cs.Initialize.Append("Text with MaterialIcons: ").Typeface(Typeface.MATERIALICONS).VerticalAlign(5dip).Append(Chr(0xE531)).PopAll
Activity.Title = cs


Images

B4X:
Dim cs As CSBuilder
cs.Initialize.Size(30).Typeface(Typeface.MONOSPACE)
cs.Append("B4A: ").Image(LoadBitmap(File.DirAssets, "b4a.png"), 40dip, 40dip, False).Append(CRLF)
cs.Append("B4i: ").Image(LoadBitmap(File.DirAssets, "b4i.png"), 40dip, 40dip, False).Append(CRLF)
cs.Append("B4J: ").Image(LoadBitmap(File.DirAssets, "b4j.png"), 40dip, 40dip, False).Append(CRLF)
cs.Append("B4R: ").Image(LoadBitmap(File.DirAssets, "b4r.png"), 40dip, 40dip, False).Append(CRLF)
cs.PopAll
Label1.Text = cs
Activity.Title = cs


The last parameter sets the image alignment. If it is true then the image will be aligned to the baseline, otherwise it is aligned to the bottom.

Clickable text

The Clickable method creates clickable text. For the event to be raised you must call cs.EnableClickEvents.
The Append method accepts a CharSequence. In the following code the CreateClickableWord sub returns a CharSequence that is then appended to the other CharSqeuence.
B4X:
Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("1")
   Dim cs As CSBuilder
   cs.Initialize.Size(30).Append("Some ").Append(CreateClickableWord("words"))
   cs.Append(" are ").Append(CreateClickableWord("clickable")).Append(".").PopAll
   Label1.Text = cs
   cs.EnableClickEvents(Label1)
End Sub

Sub CreateClickableWord(Text As String) As CSBuilder
   Dim cs As CSBuilder
   Return cs.Initialize.Underline.Color(0xFF00D0FF).Clickable("word", Text).Append(Text).PopAll
End Sub

Sub Word_Click (Tag As Object)
   Log($"You have clicked on word: ${Tag}"$)
End Sub


ListView also supports CharSequence items

B4X:
For i = 1 To 100
   ListView1.AddSingleLine(cs.Initialize.Color(Rnd(0xFF000000, -1)).Alignment("ALIGN_CENTER").Append($"Item #${i}"$).PopAll)
Next


This allows us to highlight the search term in SearchView:



Center aligned text

B4X:
MsgboxAsync(cs.Initialize.Alignment("ALIGN_CENTER").Append($"Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nam tristique metus eget sem sollicitudin, vel pulvinar nisl interdum. In sed ullamcorper lacus.
Duis ultricies urna eget faucibus ullamcorper. Donec maximus egestas tortor, vitae suscipit est varius in
Donec at arcu ut odio hendrerit molestie. Curabitur molestie felis enim, ac sodales sapien posuere sit amet."$).PopAll, _
cs.Initialize.Typeface(Typeface.FONTAWESOME).Color(0xFF01FF20).Size(40).Append(Chr(0xF17B) & " " & Chr(0xF17B) & " "& Chr(0xF17B)).PopAll)
 
Last edited:

Filippo

Expert
Licensed User
Thank you Erel!
But you're too fast, I'm not coming with your changes. ;)
 

Beja

Expert
Licensed User
Thank you Erel, that's great addition for RichEidt view, we owe you a lot!
 

Widget

Well-Known Member
Licensed User
Erel,

Is this possible with CS? (You've got my little grey cells working overtime with CS and I'm just thinking out loud here, so a simple yes or no will do)

1) In Delphi we could align the icon to the Left, Top, Right, Bottom of a button. Will this be possible with CS if there is one icon with multiple lines of text?

Example. If the icon is left aligned to the button then the lines will always appear to the right of the icon. If there are several lines of text, the lines of text should always appear to the right of the icon's right edge regardless of how many lines there are, as if there were two "panels" on the button. Icon "panel" on the left and text "panel" on the right. This means the left edges of the lines will all line up, and the lowest lines if they exceed the height of the icon, will NOT wrap underneath the icon to the left edge of the button. (This makes the button look ugly because the left edge of the text does not line up.)​

2) If #1 is not possible, it should be easy enough to write a component that draws the icon on the button's canvas and the text to the right of the button using two different positions. Can I assume DrawText() will draw the CS onto a canvas and it displays the attributes like color & typeface ok?

3) If #2 & #3 are not possible, then I'll create a custom view from a panel and just put 2 panels on it (icon panel and text panel) and have a label for each. This may be the best solution because the text can now have its own alignment. I can then use CS to control the color and style of each panel's label.

TIA
 

corwin42

Expert
Licensed User
Great feature.

Will there be a new CharSequence datatype ?

I think most of my older libraries already support CharSequences where possible. The newer ones do not because there was no native support for CharSequence in B4A until now. I will check my libraries if they support CharSequences.

Will RemoteViews.SetText() support CharSequences directly? Currently I use a dirty hack together with agrahams RichText library to colorize a text in a widget:

B4X:
Public Sub SetColorLabel(pName As String, pRv As RemoteViews, pText As RichString)
    Dim Obj1 As Reflector
    Dim args(2) As Object
    Dim types(2) As String
    Obj1.Target = pRv ' a RemoteViewsWrapper

    ' first we need the Id of the view
    args(0) = Obj1.GetProcessBA("Widget4x1")
    types(0) = "anywheresoftware.b4a.BA"
    args(1) = pName
    types(1) = "java.lang.String"
    args(0) = Obj1.RunMethod4("getIdForView", args, types) ' get the view Id
    types(0) = "java.lang.int"
    ' now the RichText charSequence
    args(1) = pText ' CharSequence
    types(1) = "java.lang.CharSequence" 
    ' now do the dirty work
    Obj1.RunMethod("checkNull") 'does some internal checking and may set current
    Obj1.Target = Obj1.GetField("current") ' a RemoteViews - get this after checkNull
    Obj1.RunMethod4("setTextViewText", args, types) 
End Sub
Hopefully this can be done easier in the future.
 

Erel

Administrator
Staff member
Licensed User
Status
Not open for further replies.
Top