Android Question [Solved by Klaus] 9Patch and MeasureStringWidth

Discussion in 'Android Questions' started by LucaMs, Jun 20, 2019.

  1. LucaMs

    LucaMs Expert Licensed 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).
     

    Attached Files:

  2. klaus

    klaus Expert Licensed 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?
     
    LucaMs likes this.
  3. LucaMs

    LucaMs Expert Licensed User

    Thank you, Klaus.


    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)
    Code:
    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: Jun 20, 2019
  4. LucaMs

    LucaMs Expert Licensed User

    Tried but it's not enough (tried but watching Germany - Serbia U21, 3-0 first half :eek::D)

    Code:
    Sub btnShow_Click
        IME1.HideKeyboard
       
        lbl9Patch.Text = EditText1.Text
       
        
    Dim lblDummy As Label
        lblDummy.Initialize(
    "")
        lblDummy.Visible = 
    False
        
    Activity.AddView(lblDummy, 00, 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
     
  5. klaus

    klaus Expert Licensed User

    Instead of using:
    Code:
    Dim cnv As Canvas
    cnv.Initialize(lblDummy)
    use:
    Code:
    Dim cnv As Canvas
    Dim bmp as Bitmap
    bmp.Initialize3(
    2dip2dip)
    cnv.Initialize(bmp)
    Here you are independant of the Label and use the minimum memory space.
     
    LucaMs likes this.
  6. LucaMs

    LucaMs Expert Licensed User

    But then I cannot use MeasureStringWidth on it.

    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".
     
  7. klaus

    klaus Expert Licensed 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: Jun 21, 2019
    LucaMs likes this.
  8. LucaMs

    LucaMs Expert Licensed User

    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?
     
  9. LucaMs

    LucaMs Expert Licensed User

    Certanly! I have to use:
    MeasuredStringWidth = cnv.MeasureStringWidth(lbl9Patch.Text, lbl9Patch.Typeface, lbl9PatchTextSize)

    [4-0]
     
  10. LucaMs

    LucaMs Expert Licensed User

    No, since lbl9Patch has the default padding, zeros.
     
  11. klaus

    klaus Expert Licensed User

    Here you are.

    The adjustment routine.

    Code:
    Sub AdjustLabel(Lbl As Label)
        
    Dim cnv As Canvas
        
    Dim bmp As Bitmap
        bmp.InitializeMutable(
    2dip2dip)
        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(2Then
            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, 
    00, 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
     

    Attached Files:

  12. LucaMs

    LucaMs Expert Licensed User

    Thank you, great Klaus.

    Your routine works well but, unfortunately, not enough.
    To test it I added these lines, before "End Sub":
    Code:
    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]
     

    Attached Files:

  13. klaus

    klaus Expert Licensed 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:
    Code:
    Sub AdjustLabel(Lbl As Label, MaxWidth As Int)
        
    Dim cnv As Canvas
        
    Dim bmp As Bitmap
        bmp.InitializeMutable(
    2dip2dip)
        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(2Then
            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, 
    00, 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: Jun 21, 2019
    LucaMs likes this.
  14. LucaMs

    LucaMs Expert Licensed User

    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: Jun 21, 2019
  15. klaus

    klaus Expert Licensed 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.

    Code:
    Sub AdjustLabel(Lbl As Label, OriginalWidth As Int, OriginalHeight As Int)
        
    Dim cnv As Canvas
        
    Dim bmp As Bitmap
        bmp.InitializeMutable(
    2dip2dip)
        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(2Then
            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, 
    00, 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
     
    LucaMs likes this.
  16. LucaMs

    LucaMs Expert Licensed User

    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...]
     
  17. LucaMs

    LucaMs Expert Licensed User

    Tested (project attached).

    Even if you "call" the routine ("tapping" on "Send") only one time, changing the text to "Lorem" immediately, you will get that bad result.
     

    Attached Files:

  18. LucaMs

    LucaMs Expert Licensed User

    No, it gets even worse.

    Thank you so much, Klaus, but don't waste any more time on my problem.

    Thank you.
     
  19. klaus

    klaus Expert Licensed User

    The latest routine:
    Code:
    Sub AdjustLabel(Lbl As Label, OriginalWidth As Int, OriginalHeight As Int)
        
    Dim cnv As Canvas
        
    Dim bmp As Bitmap
        bmp.InitializeMutable(
    2dip2dip)
        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(2Then
            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, 
    00, 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.
     
    LucaMs likes this.
  20. LucaMs

    LucaMs Expert Licensed User

    Thank you, Klaus.

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

    Post #6 (of 20)
    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.

    "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: Jun 21, 2019
Loading...
  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice