Android Question [Solved by Klaus] 9Patch and MeasureStringWidth

LucaMs

Expert
Licensed User
Longtime User
If there a "conflict" between 9Patch and canvas MeasureStringWidth or, as always :D, I'm doing something wrong?

If I use canvas MeasureStringWidth after applying the 9 patch I get a strange result (see the attached project, please).
 

Attachments

  • NineAndMeasureWidth.zip
    13.5 KB · Views: 245

klaus

Expert
Licensed User
Longtime User
You should read the documentation for Canvas.
A Canvas is an object that draws on other views or (mutable) bitmaps.
When the canvas is initialized and set to draw on a view, a new mutable bitmap is created for that view background, the current view's background
is copied to the new bitmap and the canvas is set to draw on the new bitmap.


When you initialize Canvas.Initialize(Label1) you change the label background to a BitmapDrawable, it is no more a NinePatchDrawable!

What exactly do you want to do with cnv.MeasureStringWidth?
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Thank you, Klaus.


What exactly do you want to do with cnv.MeasureStringWidth?
I want to set the label width at runtime; initially, I set it to a maximum I want to allow (85%x) but if the text width (+ label hor.paddings) is less than this max the label width should be set to this measure.

This code... almost works (but only almost :D)
B4X:
Sub btnShow_Click
    IME1.HideKeyboard
 
    lbl9Patch.Text = EditText1.Text
 
    Dim cnv As Canvas
    cnv.Initialize(lbl9Patch)
    Dim MeasuredStringWidth As Int
    MeasuredStringWidth = cnv.MeasureStringWidth(lbl9Patch.Text, lbl9Patch.Typeface, lbl9Patch.TextSize)

    SetNinePatchDrawable(lbl9Patch, "whiteleft")
 
    If MeasuredStringWidth < mBalloonMaxWidth Then
        lbl9Patch.Width = MeasuredStringWidth + lbl9Patch.Padding(0) + lbl9Patch.Padding(2)
        lbl9Patch.Background = Null
        SetNinePatchDrawable(lbl9Patch, "whiteleft")
    End If
 
    AdjustLabelHeight(lbl9Patch)

End Sub

I cannot add the paddings to the MeasuredStringWidth because it is set only after the SetNine...
I'm right now thinking that maybe I should use the same trick you used in the AdjustLabelHeight routine, I have to add a temporary label.
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
I'm right now thinking that maybe I should use the same trick you used in the AdjustLabelHeight routine, I have to add a temporary label.
Tried but it's not enough (tried but watching Germany - Serbia U21, 3-0 first half :eek::D)

B4X:
Sub btnShow_Click
    IME1.HideKeyboard
   
    lbl9Patch.Text = EditText1.Text
   
    Dim lblDummy As Label
    lblDummy.Initialize("")
    lblDummy.Visible = False
    Activity.AddView(lblDummy, 0, 0, lbl9Patch.Width, lbl9Patch.Height)
    lblDummy.Typeface = lbl9Patch.Typeface
    lblDummy.TextSize = lbl9Patch.TextSize
    lblDummy.Text = lbl9Patch.Text
    SetNinePatchDrawable(lblDummy, "whiteleft")
    Dim cnv As Canvas
    cnv.Initialize(lblDummy)
    Dim MeasuredStringWidth As Int
    MeasuredStringWidth = cnv.MeasureStringWidth(lblDummy.Text, lblDummy.Typeface, lblDummy.TextSize)
    MeasuredStringWidth = MeasuredStringWidth + lblDummy.Padding(0) + lblDummy.Padding(2)
    lblDummy.RemoveView
   
    If MeasuredStringWidth < mBalloonMaxWidth Then
        lbl9Patch.Width = MeasuredStringWidth
    End If
    SetNinePatchDrawable(lbl9Patch, "whiteleft")
   
    AdjustLabelHeight(lbl9Patch)

End Sub
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
Instead of using:
B4X:
Dim cnv As Canvas
cnv.Initialize(lblDummy)
use:
B4X:
Dim cnv As Canvas
Dim bmp as Bitmap
bmp.Initialize3(2dip, 2dip)
cnv.Initialize(bmp)
Here you are independant of the Label and use the minimum memory space.
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
Sure you can!!!
In: MeasuredStringWidth = cnv.MeasureStringWidth(lblDummy.Text, lblDummy.Typeface, lblDummy.TextSize)
you give everything you need for the calculation, independant of the target view!
Have you tested it?
I am sure you haven't!
But I have tested it!
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Sure you can!!!
In: MeasuredStringWidth = cnv.MeasureStringWidth(lblDummy.Text, lblDummy.Typeface, lblDummy.TextSize)
you give everything you need for the calculation, independant of the target view!
Have you test it?
I am sure you haven't!
But I have tested it!
I probably shouldn't do these tests while watching the football game and without having lunch yet :eek::D...
but if I still use lblDummy what is the memory savings?
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
Here you are.

The adjustment routine.

B4X:
Sub AdjustLabel(Lbl As Label)
    Dim cnv As Canvas
    Dim bmp As Bitmap
    bmp.InitializeMutable(2dip, 2dip)
    cnv.Initialize2(bmp)
    Dim MeasuredStringWidth As Int 'ignore
    MeasuredStringWidth = cnv.MeasureStringWidth(Lbl.Text, Lbl.Typeface, Lbl.TextSize)
 
    If MeasuredStringWidth < Lbl.Width - Lbl.Padding(0) - Lbl.Padding(2) Then
        Lbl.Width = MeasuredStringWidth + Lbl.Padding(0) + Lbl.Padding(2)
    Else
        Dim lblDummy As Label
        lblDummy.Initialize("")
        Dim Parent As Panel = Lbl.Parent
        Parent.AddView(lblDummy, 0, 0, Lbl.Width - Lbl.Padding(0) - Lbl.Padding(2), Lbl.Height - Lbl.Padding(1) - Lbl.Padding(3))
        lblDummy.Text = Lbl.Text
        lblDummy.TextSize = Lbl.TextSize
        lblDummy.Typeface = Lbl.Typeface
        Dim su As StringUtils
        Lbl.Height = su.MeasureMultilineTextHeight(lblDummy, Lbl.Text) + Lbl.Padding(1) + Lbl.Padding(3)
        lblDummy.RemoveView
    End If
End Sub


And the result:

upload_2019-6-21_9-9-40.png
 

Attachments

  • NineAndMeasureWidth1.zip
    13.7 KB · Views: 238
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Thank you, great Klaus.

Your routine works well but, unfortunately, not enough.
To test it I added these lines, before "End Sub":
B4X:
Sleep(2000)
Label2.Text = "Perfect"
AdjustLabel(Label2)
and this is the result:
upload_2019-6-21_10-14-49.png



Something about Padding and 9patches is probably not exactly what we think.

I was doing yet another test and after I saw your post. So I put your new routine in the attached project; in this project you can also see a label that uses your routine but without the SetNinePatchDrawable applied to it.

[You speak many languages, so you know the meaning of "Ennesimo" ("Nth") :(:D]
 

Attachments

  • Ennesimo9PatchTest.zip
    16.2 KB · Views: 238
Upvote 0

klaus

Expert
Licensed User
Longtime User
Your result is not surprising to me.
The current adjustment routine compares the text width with the current label width.
In your case, the Label width was already reduced and the new text width is wider than the current label width.
If you want to call the routine several times you must add the max width to the routine and memorize the original width of the labels.

New routine:
B4X:
Sub AdjustLabel(Lbl As Label, MaxWidth As Int)
    Dim cnv As Canvas
    Dim bmp As Bitmap
    bmp.InitializeMutable(2dip, 2dip)
    cnv.Initialize2(bmp)
    Dim MeasuredStringWidth As Int 'ignore
    MeasuredStringWidth = cnv.MeasureStringWidth(Lbl.Text, Lbl.Typeface, Lbl.TextSize)
 
    If MeasuredStringWidth < MaxWidth - Lbl.Padding(0) - Lbl.Padding(2) Then
        Lbl.Width = MeasuredStringWidth + Lbl.Padding(0) + Lbl.Padding(2)
    Else
        Lbl.Width = MaxWidth  'reset the original width
        Dim lblDummy As Label
        lblDummy.Initialize("")
        Dim Parent As Panel = Lbl.Parent
        Parent.AddView(lblDummy, 0, 0, Lbl.Width - Lbl.Padding(0) - Lbl.Padding(2), Lbl.Height - Lbl.Padding(1) - Lbl.Padding(3))
        lblDummy.Text = Lbl.Text
        lblDummy.TextSize = Lbl.TextSize
        lblDummy.Typeface = Lbl.Typeface
        Dim su As StringUtils
        Lbl.Height = su.MeasureMultilineTextHeight(lblDummy, Lbl.Text) + Lbl.Padding(1) + Lbl.Padding(3)
        lblDummy.RemoveView
    End If
End Sub

upload_2019-6-21_10-57-0.png


upload_2019-6-21_10-58-0.png


upload_2019-6-21_10-59-1.png


In the third case the width is the original width.
If you want to reduce the width in cases like this you will need to get each line and get the widest one.
 
Last edited:
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Your result is not surprising to me.
The current adjustment routine compares the text width with the current label width.
In your case, the Label width was already reduced and the new text width is wider than the current label width.
If you want to call the routine several times you must add the max width to the routine and memorize the original width of the labels.
With "max width" I think you're referring to what I named mBalloonMaxWidth in my test project; I tried your new routine, passing this value to it, but the problem seems to be the same:
upload_2019-6-21_11-14-25.png


[a few pixels!]


[I need a triple coffee this morning; pity that it is decaffeinated!]
 
Last edited:
Upvote 0

klaus

Expert
Licensed User
Longtime User
How did you get the image above?
The width of the Label is too small!

MaxWidth is the original width of the Label.
I found that the routine needs also the original height when you set a short text after a long text.

B4X:
Sub AdjustLabel(Lbl As Label, OriginalWidth As Int, OriginalHeight As Int)
    Dim cnv As Canvas
    Dim bmp As Bitmap
    bmp.InitializeMutable(2dip, 2dip)
    cnv.Initialize2(bmp)
    Dim MeasuredStringWidth As Int 'ignore
    MeasuredStringWidth = cnv.MeasureStringWidth(Lbl.Text, Lbl.Typeface, Lbl.TextSize)
    
    Lbl.Width = OriginalWidth
    Lbl.Height = OriginalHeight
    
    If MeasuredStringWidth < OriginalWidth - Lbl.Padding(0) - Lbl.Padding(2) Then
        Lbl.Width = MeasuredStringWidth + Lbl.Padding(0) + Lbl.Padding(2)
    Else
        Dim lblDummy As Label
        lblDummy.Initialize("")
        Dim Parent As Panel = Lbl.Parent
        Parent.AddView(lblDummy, 0, 0, Lbl.Width - Lbl.Padding(0) - Lbl.Padding(2), Lbl.Height - Lbl.Padding(1) - Lbl.Padding(3))
        lblDummy.Text = Lbl.Text
        lblDummy.TextSize = Lbl.TextSize
        lblDummy.Typeface = Lbl.Typeface
        Dim su As StringUtils
        Lbl.Height = su.MeasureMultilineTextHeight(lblDummy, Lbl.Text) + Lbl.Padding(1) + Lbl.Padding(3)
        lblDummy.RemoveView
    End If
End Sub
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
How did you get the image above?
The width of the Label is too small!
In the Activity_create I set a global variable (mBalloonMaxWidth) to 85%x and the Label width to this value.
So, if the Label is too small it is due to the resizing :(, the part of code after "Else"; maybe should I make the 9png smaller?

[I have yet to test your new routine...]
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
The latest routine:
B4X:
Sub AdjustLabel(Lbl As Label, OriginalWidth As Int, OriginalHeight As Int)
    Dim cnv As Canvas
    Dim bmp As Bitmap
    bmp.InitializeMutable(2dip, 2dip)
    cnv.Initialize2(bmp)
    Dim MeasuredStringWidth As Int 'ignore
    MeasuredStringWidth = cnv.MeasureStringWidth(Lbl.Text, Lbl.Typeface, Lbl.TextSize) + 2dip
 
    Lbl.Width = OriginalWidth
    Lbl.Height = OriginalHeight
  
    If MeasuredStringWidth < OriginalWidth - Lbl.Padding(0) - Lbl.Padding(2) Then
        Lbl.Width = MeasuredStringWidth + Lbl.Padding(0) + Lbl.Padding(2)
    Else
        Dim lblDummy As Label
        lblDummy.Initialize("")
        Dim Parent As Panel = Lbl.Parent
        Parent.AddView(lblDummy, 0, 0, Lbl.Width - Lbl.Padding(0) - Lbl.Padding(2), Lbl.Height - Lbl.Padding(1) - Lbl.Padding(3))
        lblDummy.Text = Lbl.Text
        lblDummy.TextSize = Lbl.TextSize
        lblDummy.Typeface = Lbl.Typeface
        Dim su As StringUtils
        Lbl.Height = su.MeasureMultilineTextHeight(lblDummy, Lbl.Text) + Lbl.Padding(1) + Lbl.Padding(3)
        lblDummy.RemoveView
    End If
End Sub

I just added 2dip to the MeasuredStringWidth value.
I testet with quite some different texts and it worked.
Maybe you could increase the value.
 
Upvote 0

LucaMs

Expert
Licensed User
Longtime User
Thank you, Klaus.

It is similar to what I did... many posts ago :D:

Post #6 (of 20)
Empirically, adding an "X" to lblDummy.Text, seems to give the desired result, but for the moment I have tried only one device and I don't really like the "empirical mode".

I think your last routine could be fine. I won't be able to perform tests on many devices (real devices, I don't like emulators) but it should work.

Maybe you could increase the value.
"Melius est abundare quam deficere"; at most the ball will be slightly too wide, with empty space, always better than having illegible letters or cut words.


Ti ringrazio moltissimo per il tuo grande aiuto, Klaus... grande Klaus!
 
Last edited:
Upvote 0
Top