Search code examples
pythonkivyscrollviewmodal-view

Kivy - How to add multiple Buttons to ScrollView inside ModalView?


I want to write a mobile app with a KivyMD like Bottom Sheet Menu. My problem with KivyMD Buttom Sheet is, when I click the button to open it, takes very long time (depends on the menu length) because the list is generated by calling the function every time the button was pressed. So I want to write my own solution for this. In my kv file I manually added 20 buttons to see, it's everything work. But i didn't find the way to do it in python file with loop. Can anyone help me please to add more than 30 buttons to modalview to be scrollable?

Here is my python file:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.core.window import Window


Builder.load_file('mylayout.kv')
Window.size = (350, 700)

class MyLayout(BoxLayout):
    pass

class MainApp(App):

    def build(self):
        return MyLayout()


MainApp().run()

an my kv file:

#:import Factory kivy.factory.Factory

<MyPopup@ModalView>
    auto_dismiss: True
    size_hint: 1, 0.5
    pos_hint: {'x': 0, 'top': 0.5}
    background_color: 0,0,0,0
    background_normal: ''
    canvas.before:
        Color:
            rgba: 48/150,84/150,150/150,1
        Rectangle:
            size: self.size
            pos: self.pos
    ScrollView:
        #do_scroll_x: False
        GridLayout:
            id: container1
            cols: 1
            size_hint: None, None
            size: root.width, 1200
            pos_hint: {'center_x': .5, 'center_y': .5}

            MyButton:
                text: '1'
                on_press:
                    root.dismiss()
                    print(1)
            MyButton:
                text: '2'
                on_press:
                    root.dismiss()
                    print(2)
            MyButton:
                text: '3'
                on_press:
                    root.dismiss()
                    print(3)
            MyButton:
                text: '4'
                on_press:
                    root.dismiss()
                    print(4)
            MyButton:
                text: '5'
                on_press:
                    root.dismiss()
                    print(5)
            MyButton:
                text: '6'
                on_press:
                    root.dismiss()
                    print(6)
            MyButton:
                text: '7'
                on_press:
                    root.dismiss()
                    print(7)
            MyButton:
                text: '8'
                on_press:
                    root.dismiss()
                    print(8)
            MyButton:
                text: '9'
                on_press:
                    root.dismiss()
                    print(9)
            MyButton:
                text: '10'
                on_press:
                    root.dismiss()
                    print(10)
            MyButton:
                text: '11'
                on_press:
                    root.dismiss()
                    print(11)
            MyButton:
                text: '12'
                on_press:
                    root.dismiss()
                    print(12)
            MyButton:
                text: '13'
                on_press:
                    root.dismiss()
                    print(13)
            MyButton:
                text: '14'
                on_press:
                    root.dismiss()
                    print(14)
            MyButton:
                text: '15'
                on_press:
                    root.dismiss()
                    print(15)
            MyButton:
                text: '16'
                on_press:
                    root.dismiss()
                    print(16)
            MyButton:
                text: '17'
                on_press:
                    root.dismiss()
                    print(17)
            MyButton:
                text: '18'
                on_press:
                    root.dismiss()
                    print(18)
            MyButton:
                text: '19'
                on_press:
                    root.dismiss()
                    print(19)
            MyButton:
                text: '20'
                on_press:
                    root.dismiss()
                    print(20)

<MyLayout>
    orientation: 'vertical'
    size: root.width, root.height

    Label:
        size_hint: 1, 0.9
        text: 'main'
        font_size: 24

    Button:
        size_hint: 1, 0.1
        text: 'menu'
        font_size: 24
        on_release: Factory.MyPopup().open()

<MyButton@Button>
    background_color: 0,0,0,0
    background_normal: ''
    canvas.before:
        Color:
            rgba: (48/255,84/255,150/255,1) if self.state == 'normal' else (43/255,108/255,229/255,1)
        Rectangle:
            size: self.size
            pos: self.pos

Solution

  • In order to build the MyPopup filled with MyButtons, you must either define those classes in the python code or use Factory to create the instances. Here is a modified version of your kv to do this:

    <MyPopup@ModalView>
        auto_dismiss: True
        size_hint: 1, 0.5
        pos_hint: {'x': 0, 'top': 0.5}
        background_color: 0,0,0,0
        background_normal: ''
        canvas.before:
            Color:
                rgba: 48/150,84/150,150/150,1
            Rectangle:
                size: self.size
                pos: self.pos
        ScrollView:
            #do_scroll_x: False
            GridLayout:
                id: container1
                cols: 1
                size_hint: None, None
                width: root.width
                height: self.minimum_height  # let the GridLayout set its own height as needeed
                pos_hint: {'center_x': .5, 'center_y': .5}
                
    <MyLayout>
        orientation: 'vertical'
        size: root.width, root.height
    
        Label:
            size_hint: 1, 0.9
            text: 'main'
            font_size: 24
    
        Button:
            size_hint: 1, 0.1
            text: 'menu'
            font_size: 24
            # on_release: Factory.MyPopup().open()
            on_release: app.open_popup()  # call app method to build MyPopup and fill it
    
    <MyButton@Button>
        background_color: 0,0,0,0
        background_normal: ''
        size_hint_y: None
        height: 20
        canvas.before:
            Color:
                rgba: (48/255,84/255,150/255,1) if self.state == 'normal' else (43/255,108/255,229/255,1)
            Rectangle:
                size: self.size
                pos: self.pos
    

    Note that the GridLayout height is set to self.minimum_height to allow for any number of MyButton children, and the MyButton height is set to a fixed value (so that GridLayout can calculate the minimum height). Also, the import of Factory is no longer needed in the kv.

    The modified python code:

    from kivy.app import App
    from kivy.factory import Factory
    from kivy.uix.boxlayout import BoxLayout
    from kivy.lang import Builder
    from kivy.core.window import Window
    
    Builder.load_file('mylayout.kv')
    Window.size = (350, 700)
    
    class MyLayout(BoxLayout):
        pass
    
    class MainApp(App):
    
        def build(self):
            return MyLayout()
    
        def open_popup(self, *args):
            # create the popup (must use Factory since MyPopup is defined in kv)
            self.popup = Factory.MyPopup()
    
            # fill the GridLayout
            grid = self.popup.ids.container1
            for i in range(60):
                grid.add_widget(Factory.MyButton(text=str(i), on_press=self.myButtPress))
    
            # open popup
            self.popup.open()
    
        def myButtPress(self, butt):
            print(butt.text)
            self.popup.dismiss()
    
    MainApp().run()