Android Question How can I combine numbers with material icons?

Seneca

Active Member
Licensed User
Hi,

Does the MaterialIcons font not include numbers?

B4X:
lblFontAwesome.Text = Chr(0xF0A2) & " A1 B2 C3" 'Numbers are shown
lblMaterialIcons.Text = Chr(0xE0B0)  & " A1 B2 C3"   'Numbers are not shown

Best regards.
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Create a label named lblMaterialIcons and set its Typeface to Material Icons. You shouldn't show any text in this label. It is only used for the Typeface.

Now you can use this sub to combine material icons characters with other characters.
B4X:
Label1.Text = CombineTextWithMaterialIcons(Chr(0xE0B0)  & " a1 B2 c3")

Sub CombineTextWithMaterialIcons(s As String) As RichString
   Dim rs As RichString
   rs.Initialize(s)
   For i = 0 To rs.Length - 1
     Log( Asc(s.CharAt(i)))
     If Asc(s.CharAt(i)) > 0xe000 Then
       rs.TypefaceCustom(lblMaterialIcons.Typeface, i, i + 1)
     End If
   Next
   Return rs
End Sub

Note that the Typeface of Label1 is NOT Material Icons.
 
Upvote 0

Widget

Well-Known Member
Licensed User
Longtime User
That works really well. :)

What I like about this solution is that you can increase the size of the icon relative to the text size. This allows you to have a large icon and shrink the text so you can use smaller buttons.

B4X:
Label1.Text = CombineTextWithMaterialIcons(Chr(0xE0B0) & " a1 B2 c3",2)    'Create icon 2x larger than text

Sub CombineTextWithMaterialIcons(s As String, aIconRelSize As Float) As RichString
    Dim rs As RichString
    rs.Initialize(s)
    For i = 0 To rs.Length - 1
        Log( Asc(s.CharAt(i)))
        If Asc(s.CharAt(i)) > 0xe000 Then
            rs.TypefaceCustom(lblMaterialIcons.Typeface, i, i + 1)
            rs.RelativeSize(aIconRelSize, i, i+1)
        End If
    Next
    Return rs
End Sub
 
Last edited:
Upvote 0

Mahares

Expert
Licensed User
Longtime User
What I like about this solution is that you can increase the size of the icon relative to the text size

I think you are better off if you use a float for the relative size, like this:
B4X:
Sub CombineTextWithMaterialIcons(s As String, aIconRelSize As Float) As RichString
Otherwise, 1.5 will be treated as 1, and if you use a relative size of .75, the icon does not even show up.
 
Upvote 0

Widget

Well-Known Member
Licensed User
Longtime User
I think you are better off if you use a float for the relative size, like this:
B4X:
Sub CombineTextWithMaterialIcons(s As String, aIconRelSize As Float) As RichString
Otherwise, 1.5 will be treated as 1, and if you use a relative size of .75, the icon does not even show up.

You're absolutely right.
I meant to use Float instead of Int. I have changed my code in #5 so it uses Float.
Thanks. :p
 
Upvote 0

Seneca

Active Member
Licensed User
Hi.

I encountered a problem when combining the two typeface. The vertical alignment of both is different.

I suppose it is a Material Icons problem, but I don't know if it will have any solution.

Regards


B4X:
Label1.Text = CombineTextWithMaterialIcons(Chr(0xEB3C)  & " 300 Km " & Chr(0xE55F) & Chr(0xE52F) & Chr(0xE530))

vertical_aligment.jpg
 
Upvote 0

Widget

Well-Known Member
Licensed User
Longtime User
Yes I noticed this too. It appears (to me) that MaterialIcons has the text vertical alignment set to Bottom so the bottom of the text is lower than the bottom of the icon.
And it appears that FontAwesome has the text's vertical alignment set to TOP so the top of the text is above the icon.

Why do they do it this way? It's all a diabolical plot to drive programmers mad of course. :eek:

I could find no simple solution for this. There may be a way to add vertical adjustments into this RichString using tokens or Span*. I'm not familiar with RichString so I will leave this puzzle up to the experts. Even if a solution is found, because you are dealing with TTF, tricks like this may not position the icon/text properly on the user's mobile device.

Another solution is to write a Sub() that uses DrawText() to put the Materialicon (or FontAwesome) icon onto a bitmap or canvas, then use DrawText() again to write the text beside the icon on the same bitmap or canvas, and then draw the bitmap onto the canvas of the button or panel. That way you can adjust the vertical height and horizontal position of each, and also use any typeface for the font you like. You will of course use Canvas.MeasureStringWidth and Canvas.MeasureStringHeight to help with positioning the text. If you have multi line text, then StringUtils.MeasureMultilineTextHeight may help.

BTW, Erel's trick also supports CRLF in the text, so you could position the text below the icon by adding "& CRLF" to the text. But the alignment is off. Hey, I did tell you it was a diabolical plot didn't I? :cool:
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
SS-2017-02-09_08.36.27.png


Code:
B4X:
Sub Process_Globals
End Sub

Sub Globals
   Private lblMaterialIcons As Label
   Private Label1 As Label
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.LoadLayout("1")   
   Label1.Text = CombineTextWithMaterialIcons(Chr(0xEB3C)  & " 300 Km " & Chr(0xE55F) & Chr(0xE52F) & Chr(0xE530), 1)
End Sub

Sub CombineTextWithMaterialIcons(s As String, aIconRelSize As Float) As RichString
   Dim rs As RichString
   rs.Initialize(s)
   For i = 0 To rs.Length - 1
     Log( Asc(s.CharAt(i)))
     If Asc(s.CharAt(i)) > 0xe000 Then
       rs.TypefaceCustom(lblMaterialIcons.Typeface, i, i + 1)
       rs.RelativeSize(aIconRelSize, i, i+1)
       rs.Subscript(i, i + 1)
       Dim jo As JavaObject = rs
       Dim shift As JavaObject
       Dim delta As Int = -6dip
       shift.InitializeNewInstance(Application.PackageName & ".main$VerticalAlignedSpan", Array(delta))
       jo.RunMethod("setSpan", Array(shift, i, i + 1, 0))
     End If
   Next
   Return rs
End Sub

#if Java
import android.text.style.*;
import android.graphics.Paint;
import android.text.TextPaint;
public static class VerticalAlignedSpan extends MetricAffectingSpan {
   int shift;
   public VerticalAlignedSpan(int shift) {
     this.shift = shift;
   }
    @Override
  public void updateDrawState(TextPaint tp) {
  tp.baselineShift += shift;
  }

  @Override
  public void updateMeasureState(TextPaint tp) {
  tp.baselineShift += shift;
  }
}
#End If

I've only tested it on a single device. I'm not sure whether the shift value should be scaled (with dip units) or not. You need to test it on multiple devices.
 
Upvote 0

Widget

Well-Known Member
Licensed User
Longtime User
SS-2017-02-09_08.36.27.png




I've only tested it on a single device. I'm not sure whether the shift value should be scaled (with dip units) or not. You need to test it on multiple devices.

Wow. Erel pulls another rabbit out of his hat. (Must be a pretty big hat 'cause there are a lot of rabbits of his running around!) :p

Maybe if enough B4A users try the code out on their devices, they can report back to see if there are any problems?

This made me think of a (good?) idea. Maybe there should be a forum called "Beta Testing" where Erel (others?) can post code (small test apps) for others to test, like this one, on their devices and report back any problems? This would greatly improve the reliability of B4x code.

What do you think?
 
Upvote 0

Mahares

Expert
Licensed User
Longtime User
I tested @Erel's snippet on 3 devices. The label looked very well scaled in all three:
Galaxy Tab2 7 inch tablet, OS 4.2.2, Scale=1
7 inch tablet, OS 5.1.1, Scale=1
Galaxy S6 phone, OS 6.0.1, Scale=4

Not to minimize the great code snippet by @Erel, is it worth the extra overhead added by the Javaobject and Richstring libraries to gain a marginal improvement in the way the label looks? Unless you are a perfectionist, most people will not notice the subtle irregularity of the vertical alignment.
 
Upvote 0

Widget

Well-Known Member
Licensed User
Longtime User
If the icon size and text size are the same, then you're right, most people may not notice it.

But the alignment problem is definitely noticeable when the icon size is larger than the text size. So if you want a large icon and a smaller text size then you have to use Erel's solution. The alternative is to have a large icon and large text (same size) and the text IMHO will occupy too much space, especially on buttons. If you want to save resources and not use Erel's approach, you can always write a Sub that draws the icon and text on the label, button or panel.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
is it worth the extra overhead added by the Javaobject and Richstring libraries to gain a marginal improvement in the way the label looks?
The overhead is negligible.
You can measure it. It takes about 1ms and we can assume that it is only called a few times when the activity is created.
 
Upvote 0

RauchG

Active Member
Licensed User
Longtime User
Hi all,

I've only tested it on a single device. I'm not sure whether the shift value should be scaled (with dip units) or not. You need to test it on multiple devices.

I get this error message:

B4X:
actsenden_combinetextwithmaterialicons (B4A line: 759)
shift.InitializeNewInstance(Application.Package
java.lang.ClassNotFoundException: com$aaa$elist$main$VerticalAlignedSpan
    at anywheresoftware.b4j.object.JavaObject.getCorrectClassName(JavaObject.java:288)
    at anywheresoftware.b4j.object.JavaObject.InitializeNewInstance(JavaObject.java:83)
    at com.aaa.elist.actsenden._combinetextwithmaterialicons(actsenden.java:1048)
    at com.aaa.elist.actsenden._activity_create(actsenden.java:434)
    at java.lang.reflect.Method.invoke(Native Method)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:179)
    at com.aaa.elist.actsenden.afterFirstLayout(actsenden.java:105)
    at com.aaa.elist.actsenden.access$000(actsenden.java:20)
    at com.aaa.elist.actsenden$WaitForLayout.run(actsenden.java:83)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5418)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

What am I missing?

Greeting
RauchG
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
What am I missing?
That this is the wrong thread for your question?

You should create a new thread for each question you have. You should not capture another thread for your questions.

Create a new Thread, post enough informations (maybe a small project to check).
 
Last edited:
Upvote 0
Top