Android Question Evenly spacing buttons vertically on any screen

pjetson

Member
Licensed User
Longtime User
I've tried to simplify this question down to the basics. Please don't be sidetracked by what the resulting screen might actually look like in portrait or landscape or even in moonscape :)

Assume that I have five buttons down the screen, and I want them to all be the same size and evenly spaced, on any screen.

I decided by observing what looked best, on the following "rules":

1. The first button should start 10% of the screen height down from the top of the screen.
2. Each button should be 15% high
3. Each button should be 18% of the screen down from the previous button, to leave a 3% high blank space between the buttons.
4. 10% of the screen width should be blank on either side of the buttons.

So, I created the following Designer script:

B4X:
'All variants script
AutoScaleAll

btn1.SetTopAndBottom(10%y, 10%y + 15%y)
btn1.SetLeftAndRight(10%x, 90%x)

btn2.SetTopAndBottom(28%y, 28%y + 15%y)
btn2.SetLeftAndRight(10%x, 90%x)

btn3.SetTopAndBottom(46%y, 46%y + 15%y)
btn3.SetLeftAndRight(10%x, 90%x)

btn4.SetTopAndBottom(64%y, 64%y + 15%y)
btn4.SetLeftAndRight(10%x, 90%x)

btn5.SetTopAndBottom(82%y, 82%y + 15%y)
btn5.SetLeftAndRight(10%x, 90%x)

This seems to produce exactly what I want, on my own phone (a Nexus 4). I haven't yet tested it on any other screen.

My question is, is this the "right" way to do it?
 

Erel

B4X founder
Staff member
Licensed User
Longtime User
Looks good. Note that you can use variables in the script:
B4X:
AutoScaleAll
h = 15%y
left = 10%x
right = 90%x
h1 = 10%y
delta = 18%y
btn1.Top = h1 + 0 * delta
btn1.Height = h
btn2.Top = h1 + 1 * delta
btn2.Height = h
btn3.Top = h1 + 2 * delta
btn3.Height = h
btn4.Top = h1 + 3 * delta
btn4.Height = h
btn5.Top = h1 + 4 * delta
btn5.Height = h
btn1.SetLeftAndRight(left, right)
btn2.SetLeftAndRight(left, right)
btn3.SetLeftAndRight(left, right)
btn4.SetLeftAndRight(left, right)
btn5.SetLeftAndRight(left, right)
The advantage of this code is that it makes it easier to modify it in the future.
If there were more than 5 buttons then it would have probably better to do it in code (with a loop) instead.
 
Upvote 0

pjetson

Member
Licensed User
Longtime User
Thanks, Erel. I had tried to use variables, but it didn't seem to work - the buttons didn't appear where they should have.

I had something like (from memory) btn1.SetTopAndBottom(h1 + (0 * delta), h1 + (0 * delta) + h)

Most likely, I was just doing something wrong :( again.
 
Upvote 0

chuck3e

Active Member
Licensed User
Longtime User
Thanks, Erel. I had tried to use variables, but it didn't seem to work - the buttons didn't appear where they should have.

I had something like (from memory) btn1.SetTopAndBottom(h1 + (0 * delta), h1 + (0 * delta) + h)

Most likely, I was just doing something wrong :( again.

Pjetson,

I might be missing something obvious here but doesn't (0 * Delta) always give zero as an answer? Or is that an string O not a zero 0 ?
 
Upvote 0

pjetson

Member
Licensed User
Longtime User
Hi, Chuck3e. Yes, you're correct - it's a zero, and (0 * Delta) will always give zero as the answer.

You'll notice in Erel's code that he uses 0 * Delta, then 1 * Delta, then 2 * Delta, and so on for each button. This is to show that the same calculation is being used each time, even though it's not actually needed in the case of 0 * Delta because there is no need to add anything for the first button.

This kind of thing makes code more maintainable.
 
Upvote 0

JohnD

Active Member
Licensed User
Longtime User
This is alot cleaner than what I currently do.

Looks good. Note that you can use variables in the script:
B4X:
AutoScaleAll
h = 15%y
left = 10%x
right = 90%x
h1 = 10%y
delta = 18%y
btn1.Top = h1 + 0 * delta
btn1.Height = h
btn2.Top = h1 + 1 * delta
btn2.Height = h
btn3.Top = h1 + 2 * delta
btn3.Height = h
btn4.Top = h1 + 3 * delta
btn4.Height = h
btn5.Top = h1 + 4 * delta
btn5.Height = h
btn1.SetLeftAndRight(left, right)
btn2.SetLeftAndRight(left, right)
btn3.SetLeftAndRight(left, right)
btn4.SetLeftAndRight(left, right)
btn5.SetLeftAndRight(left, right)
The advantage of this code is that it makes it easier to modify it in the future.
If there were more than 5 buttons then it would have probably better to do it in code (with a loop) instead.
 
Upvote 0

RandomCoder

Well-Known Member
Licensed User
Longtime User
My own solution is to call a function which places the button in the correct position and assigns the required icon and tag to the button. All buttons are located on a panel which I restrict from getting too big. Therefore on large tablets the panel is centralised along the bottom of the screen (or right hand side if in landscape) and the buttons are kept to a reasonable size.
B4X:
Public Sub audioControl_Display(show As Boolean)
    If show Then
        If Not(audioControlPanel.IsInitialized) Then
            ' Initialise audioControlPanel and add to activity
            audioControlPanel.Initialize("audioControlPanel")
            audioControlPanel.Color = 0xFFD9D7DE
            ' Determine if in landscape or portrait mode
            Dim orientation As String
            orientation = Format.GetScreenOrientation
            If orientation = "portrait" Then
                Activity.AddView(audioControlPanel, 0, Activity.Height - 75dip, 100%x, 75dip)
            Else
                Activity.AddView(audioControlPanel, Activity.Width - 75dip, 0, 75dip, 100%y)
            End If
            ' Create audiobuttonpanel with textured background and add to audiocontrolpanel to produce a bordered effect
            Dim audioButtonPanel As Panel
            audioButtonPanel.Initialize("audioControlPanel")
            audioButtonPanel.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "audio control background.png", 100%x, 100%y))
            audioControlPanel.AddView(audioButtonPanel, 2dip, 2dip, audioControlPanel.Width-4dip, audioControlPanel.Height-4dip)
            ' Add buttons to the panel
            init_audioButton(0, "previous", audioButtonPanel, orientation)
            init_audioButton(1, "rewind", audioButtonPanel, orientation)
            If Music_Service.playerStatus = "stopped" Then
                init_audioButton(2, "play", audioButtonPanel, orientation)
            Else
                init_audioButton(2, "stop", audioButtonPanel, orientation)
            End If
            init_audioButton(3, "forward", audioButtonPanel, orientation)
            init_audioButton(4, "next", audioButtonPanel, orientation)
        End If
        audioControlPanel.Visible = True
    Else
        If audioControlPanel.IsInitialized Then
            audioControlPanel.Visible = False
        End If
    End If
    ' Refresh CustomListView height to account for Audio Control Panel if visible or hidden
    clvAdjustHeight
End Sub

Private Sub init_audioButton(index As Int, function As String, btnPanel As Panel, orientation As String)
    Dim btnWidth, btnHeight As Int
    Dim btn As Button
    ' Create a new audio control button
    btn.Initialize("audioButton")
    ' Assign the button with the required function eg."previous", "Play", "Stop" etc.
    btn.Tag = function
    ' Screen orientation affects where audio control panel is placed
    If orientation = "portrait" Then
        ' Buttons to be placed horizontally with a 5dip gap both sides of audio control panel and 1dip gap top and bottom
        btnWidth = (btnPanel.Width - 10dip) / 5
        btnHeight = btnPanel.Height - 4dip
        ' The index sets position of the button
        btnPanel.AddView(btn,  5dip + (btnWidth * index), 2dip, btnWidth, btnHeight)
    Else
        ' Buttons to be placed vertically with a 5dip gap top and bottom and 1dip gap on either sides
        btnWidth = btnPanel.Width - 4dip
        btnHeight = (btnPanel.Height - 10dip) / 5
        ' The index sets position of the button
        btnPanel.AddView(btn, 2dip, 5dip + (btnHeight * index), btnWidth, btnHeight)  
    End If
    ' Apply the button with the required image
    btn.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "audio " & function & ".png", btnWidth, btnHeight))
End Sub

Public Sub audioButton_Click()
    If Music_Service.playerInitialised Then
        Dim btn As Button = Sender
        Select Case btn.Tag
            Case "previous"
                Music.PlayPreviousTrack
            Case "rewind"
          
            Case "stop"
                If Music_Service.musicStream.IsPlaying Then
                    Music_Service.musicStream.Pause
                    Music_Service.playerStatus = "stopped"
                    btn.Tag = "play"
                    btn.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "audio " & "play" & ".png", (audioControlPanel.Width - 10dip) / 5, audioControlPanel.Height - 4dip))
                End If
            Case "play"
                If Not(Music_Service.musicStream.IsPlaying) AND Music.playList.Size >= 1 Then
                    Music_Service.musicStream.Play
                    Music_Service.playerStatus = "playing"
                    btn.Tag = "stop"
                    btn.SetBackgroundImage(LoadBitmapSample(File.DirAssets, "audio " & "stop" & ".png", (audioControlPanel.Width - 10dip) / 5, audioControlPanel.Height - 4dip))
                End If
            Case "forward"
          
            Case "next"
                Music.PlayNextTrack
        End Select
    End If
End Sub

It would be possible to call init_audioButton(index As Int, function As String, btnPanel As Panel, orientation As String) in a loop and rename the background images to 1.png, 2.png and so on but I prefer to assign meaningful names. This supports both screen orientations and dynamically assigns button functions i.e. normally the play button is displayed when first showing the audio controls panel but this changes to a stop button if playing music. On a screen orientation change a check is made to see whether to display a play icon or stop icon.

Regards,
RandomCoder
 
Upvote 0
Top