pythonkivyscrollviewstacklayout

Kivy: Screen containing ScrollView, StackLayout and Labels is all out of place


I am trying to have a screen which contains a StackLayout. This StackLayout contains several labels of short to long length. The whole content of the StackLayout should also be scrollable with ScrollView. I tried the setup below, but basically every Label is out of place. I have tried several alternative setups based on my understanding of kivy documentation and of (somewhat) similar questions on the internet, but non of them really worked.

Here is a minimal working version of the code:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.scrollview import ScrollView
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window

Window.size = (350, 600)


class RootWindow(Screen):
    def __init__(self, **kwargs): 
        super(RootWindow, self).__init__(**kwargs)

        self.searchButton = Button(text="Search",
                                   size_hint=(1,0.8))
        self.searchButton.bind(on_press=self.search)
        self.add_widget(self.searchButton)
        # some irrelevant code

    def search(self,instance):

        #here some omitted code which grabs some strings from a SQLite db and saves results in src, ingsList and steps

        src = 'Lorem ipsum'
        ingsList = ['Lorem', 'ipsum', 'dolor', 'sit amet']
        WMan.transition.direction = 'left'
        WMan.current = 'second'

        # theses should be aligned at center of the screen
        WMan.current_screen.title.add_widget(Label(text=src,
                                                   size_hint=(1,None)))

        WMan.current_screen.ingredients.add_widget(Label(text="Ingredients:",
                                                           text_size=(self.width, None),
                                                           size_hint=(1,None),
                                                           height='30sp'))
        # these should be left aligned
        for i in range(0,len(ingsList)):
            WMan.current_screen.ingredients.add_widget(Label(text=ingsList[i],
                                                                  text_size=(self.width, None),
                                                                  size_hint=(0.9,None),
                                                                  height='20sp'))
        # center aligned
        WMan.current_screen.ingredients.add_widget(Label(text="Steps:",
                                                           text_size=(self.width, None),
                                                           size_hint=(1,None),
                                                           height='30sp'))

        # this should be left aligned (or, ideally, justified)
        steps = "Duis finibus risus tempor nisl scelerisque, quis facilisis augue pretium. Nullam sit amet nibh ex. Pellentesque lobortis eget ipsum a congue. Nunc luctus odio sit amet arcu interdum, id pharetra urna semper. Proin at turpis vel neque facilisis pretium ut sed massa. Phasellus elit diam, elementum at tempus non, eleifend quis libero. Integer convallis tortor eget mattis eleifend."
        WMan.current_screen.ingredients.add_widget(Label(text=steps,
                                                                       text_size=(self.width, None),
                                                                       size_hint=(1,None),
                                                                       pos_hint=(None,None),
                                                                       height='10sp'
                                                                       ))



class SecondWindow(Screen):
    def __init__(self, **kwargs): 
        super(SecondWindow, self).__init__(**kwargs)

        self.title = StackLayout(pos_hint={"x":0.05, "top":0.9},
                                 size_hint = (0.9,0.2))
        self.add_widget(self.title)

        self.ingredients = StackLayout(pos_hint={"x":0.05, "top": 0.8},
                                       size_hint=(0.9,None),
                                       size_hint_y=None,
                                       orientation='lr-tb',
                                       spacing = (0,2))
        self.ingredients.bind(minimum_height = self.ingredients.setter('height'))

        self.scroll = ScrollView(size_hint=(1,None),
                                 size=(self.width, Window.height),
                                 pos_hint={"x": 0.05, "top":0.8})
        self.scroll.add_widget(self.ingredients)
        self.add_widget(self.scroll)

WMan = ScreenManager()
WMan.add_widget(RootWindow(name='root'))  
WMan.add_widget(SecondWindow(name='second'))

class MyApp(App):
    def build(self):
        return WMan


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

Solution

  • This is more easily solved by using the kv language. Here is a modified version of your code that does what I think you want:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.button import Button
    from kivy.uix.label import Label
    from kivy.uix.screenmanager import ScreenManager, Screen
    from kivy.core.window import Window
    
    Window.size = (350, 600)
    
    class LeftAlignedLabel(Label):
        pass
    
    Builder.load_string('''
    <LeftAlignedLabel>:
        size_hint: 1, None
        height: '20sp'
        text_size: self.size
        halign: 'left'
    <SecondWindow>:
        StackLayout:
            id: title_stack
            pos_hint: {"x":0.05, "top":0.9}
            size_hint: (0.9,0.2)
            Label:
                id: title
                size_hint: (1, None)
                height: self.texture_size[0]
        ScrollView:
            id: scroll
            size_hint: (0.9,0.7)
            pos_hint: {"x": 0.05, "top":0.7}
            StackLayout:
                size_hint: (0.9,None)
                orientation: 'lr-tb'
                spacing: (0,2)
                height: self.minimum_height
                Label:
                    text: 'Ingredients:'
                    size_hint: (1,None)
                    text_size: self.size
                    halign: 'left'
                    height: '30sp'
                BoxLayout:
                    id: ingredients
                    orientation: 'vertical'
                    size_hint: (1, None)
                    height: self.minimum_height
                Label:
                    text: 'Steps:'
                    size_hint: (1,None)
                    height: '30sp'
                Label:
                    id: steps
                    text_size: (self.width, None)
                    size_hint: (1,None)
                    height: self.texture_size[1]  # adjusts height according to text
    ''')
    
    
    class RootWindow(Screen):
        def __init__(self, **kwargs):
            super(RootWindow, self).__init__(**kwargs)
    
            self.searchButton = Button(text="Search",
                                       size_hint=(1,0.8))
            self.searchButton.bind(on_press=self.search)
            self.add_widget(self.searchButton)
            # some irrelevant code
    
        def search(self,instance):
    
            #here some omitted code which grabs some strings from a SQLite db and saves results in src, ingsList and steps
    
            src = 'Lorem ipsum'
            ingsList = ['Lorem', 'ipsum', 'dolor', 'sit amet']
    
            WMan.transition.direction = 'left'
            WMan.current = 'second'
    
            # theses should be aligned at center of the screen
            # WMan.current_screen.title.add_widget(Label(text=src,
            #                                            size_hint=(1,None)))
            WMan.current_screen.ids.title.text = src
    
            # these should be left aligned
            for i in range(0,len(ingsList)):
                WMan.current_screen.ids.ingredients.add_widget(LeftAlignedLabel(text=ingsList[i]))
    
            # this should be left aligned (or, ideally, justified)
            steps = "Duis finibus risus tempor nisl scelerisque, quis facilisis augue pretium. Nullam sit amet nibh ex. Pellentesque lobortis eget ipsum a congue. Nunc luctus odio sit amet arcu interdum, id pharetra urna semper. Proin at turpis vel neque facilisis pretium ut sed massa. Phasellus elit diam, elementum at tempus non, eleifend quis libero. Integer convallis tortor eget mattis eleifend."
            WMan.current_screen.ids.steps.text = steps
    
    
    class SecondWindow(Screen):
        pass
    
    
    WMan = ScreenManager()
    WMan.add_widget(RootWindow(name='root'))
    WMan.add_widget(SecondWindow(name='second'))
    
    
    class MyApp(App):
        def build(self):
            return WMan
    
    
    if __name__ == "__main__":
        MyApp().run()
    

    The LeftAlignedLabel class is used to get the ingredients list left aligned.

    Also, using the kv language makes it easier to see questionable structures. For example, the title Label is the only child of the first StackLayout. Generally, if you are not planning to add additional children, a layout with just one child can be replaced by just the child.