Search code examples
pythonkivy

Kivy center button


I'm making a simple test app in Kivy for learning:

class MyWidget(GridLayout):
    def __init__(self, **kwargs):
        super(MyWidget, self).__init__(**kwargs)        
        Window.clearcolor = (1, 1, 1, 1)

class PhoneApp(App):
    def build(self):
        return MyWidget()

if __name__ == "__main__":
    PhoneApp().run()

This my .kv:

#:kivy 2.1.0
<Label>
    size_hint_y: None
    height: self.texture_size[1]
    color: 0, 0, 0, 1

<Image>
    size_hint_y: None
    height: self.texture_size[1]

<Button>
    size_hint_x: None
    size_hint_y: None
    width: self.texture_size[0]
    height: self.texture_size[1]

<GridLayout>
    cols: 1
    size_hint: (0.5, 0.9)
    pos_hint: {"center_x": 0.5, "center_y": 0.5}

<MyWidget>:
    Image:
        source: "captus.png"
    
    Label:
        text: "Test Message"        

    Button:
        text: "Second test"

The problem is that the button isn't aligned, as you can see in the following image:

enter image description here

The green button "Second test" should be centered, in the middle of the window, like "Test Message" is.

I've been playing with pos_hint: {'center_x':0.5, 'center_y':0.5} but I can't manage to center that button...

How can I center the "Second test" button?


Solution

  • The problem is that a GridLayout does not honor pos_hint at all. So getting a widget centered in a GridLayout cell takes a bit of planning.

    One way is to just make the Button fill the width of the GridLayout cell. That doesn't actually center the Button, but the text of the Button will be centered. To so this, just remove the size_hint_x: None line from the <Button>: rule in your kv.

    Another way is to put the Button in a container that fills the GridLayout cell, and center the Button in that container. An AnchorLayout serves just that purpose. Try replacing the Button part of your <MyWidget>: rule with:

    AnchorLayout:
        size_hint_y: None
        height: butt.height  # same height as the Button
        Button:
            id: butt
            text: "Second test"   
    

    A third option is to not use GridLayout. If you are only using 1 column or 1 row, a BoxLayout may be a better approach. And a BoxLayout supports pos_hints (partially, see the documentation). To use this approach, change MyWidget to extend BoxLayout:

    class MyWidget(BoxLayout):
        def __init__(self, **kwargs):
            super(MyWidget, self).__init__(**kwargs)
            Window.clearcolor = (1, 1, 1, 1)
    

    Then you can use the pos_hint as suggested by @mohammad-alqashqish in the kv:

    <Label>
        size_hint_y: None
        height: self.texture_size[1]
        color: 0, 0, 0, 1
    
    <Image>
        size_hint_y: None
        height: self.texture_size[1]
    
    <Button>
        size_hint_x: None
        size_hint_y: None
        width: self.texture_size[0]
        height: self.texture_size[1]
    
    <GridLayout>
        cols: 1
        size_hint: (0.5, 0.9)
        pos_hint: {"center_x": 0.5, "center_y": 0.5}
    
    <MyWidget>:
        orientation: 'vertical'
        Image:
            source: "tester.png"
        
        Label:
            text: "Test Message"  
    
        Button:
            text: "Second test"   
            pos_hint: {'center_x': 0.5}
    

    One other thing to note. When you make a rule in kv for a standard widget, like <Label>:, that rule will be applied to every Label you create in your app after that kv is loaded.