'Python Kivy: Scrollview and positioning of text

I am trying to get kind of an app with kivy. The purpose to start with is to choose a cocktail from a list of cocktails. I am also using pure python, no kv language. So far I was glad that I somehow managed to translate all the kv examples and instructions online, but now I conquered two problems, which I am not able to solve.

Problem 1: The Scrollview is not really scrolling. It shows the animation, but always jumps back to the top. I read a lot of advices to set the height to the minimum_height, but still no effort. Maybe something with the self. ... thing is not working but I can not figure it out.

Problem 2: When I push one of the buttons, it shows a new screen with a description of the cocktail (which will be extended). So far so good, but if I go back to the menu (arrow top left), and choose another cocktail, the Label changed position. But only after the first time, after that it stays at the same (wrong) position.

I removed a lot of my code, but as I assume the problem is located somewhere in the definitions of my elements, I kept them in the Code example, so it is quite long, but I hope one can still use it for troubleshooting.

import kivy
from kivy.config import Config
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.properties import ListProperty
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from functools import partial
kivy.require('2.0.0')
Config.set('graphics', 'width', '380')
Config.set('graphics', 'height', '730')

cocktails = [["AA"],["BB"],["CC"],["DD"],["EE"],["FF"],["GG"],["HH"],["II"],["JJ"],["KK"],["LL"],["MM"]]
cur_cocktail = 0


class VBoxLayout(BoxLayout):

    def __init__(self):
        super().__init__(orientation="vertical")


class TopRowBoxLayout(BoxLayout):

    def __init__(self):
        super().__init__(size_hint_x=1, size_hint_y=0.08, orientation="horizontal")


class ColoredBackgroundMixin:
    _color = None
    _rect = None
    background_color = ListProperty([0.0, 0.0, 0.0, 1.0])

    def __init__(self, *, background_color, **kwargs):
        super().__init__(**kwargs)
        with self.canvas.before:
            self.background_color = background_color
            self._color = Color(*background_color)
            self._rect = Rectangle(size=self.size, pos=self.pos)
            self.bind(size=self._update_rect, pos=self._update_rect, background_color=self._update_rect)

    def _update_rect(self, instance, value):
        self._color.rgba = instance.background_color
        self._rect.pos = instance.pos
        self._rect.size = instance.size


class ColoredLabel(ColoredBackgroundMixin, Label):
    pass


class TopRowBackButton(Button):

    def __init__(self):
        super().__init__(size_hint_x=0.15, size_hint_y=1, text="<-", background_normal="", background_color=(0.55, 0.12, 0.12, 1))


class TopRowFiller(ColoredLabel):

    def __init__(self):
        super().__init__(size_hint_x=0.85, size_hint_y=1, text="", background_color=[0.55, 0.12, 0.12, 1])


class TopRowFillerFull(ColoredLabel):

    def __init__(self):
        super().__init__(background_color=[0.55, 0.12, 0.12, 1], text="")


class StdScrollView(ScrollView):

    def __init__(self):
        super().__init__(do_scroll_x=False, do_scroll_y=True)


class MainLayout(GridLayout):

    def __init__(self):
        super().__init__(cols=1, padding=(0.06*self.width, 0.06*self.width), spacing=0.02*self.width,
                         size_hint_y=None, height=self.minimum_height)


class StdButton(Button):

    def __init__(self, text):
        self.text = text
        super().__init__(text=self.text, size_hint=(1, None), height=0.6*self.width)


class ChooseScreen(Screen):

    def __init__(self, **kwargs):
        super(ChooseScreen, self).__init__(**kwargs)
        self.define_layout()

    def define_layout(self):
        self.base_layout = VBoxLayout()
        self.add_widget(self.base_layout)

        self.header = TopRowBoxLayout()
        self.base_layout.add_widget(self.header)

        self.header.add_widget(TopRowFillerFull())

        self.scroll_area = StdScrollView()
        self.base_layout.add_widget(self.scroll_area)

        self.main_layout = MainLayout()
        self.scroll_area.add_widget(self.main_layout)

        self.buttons = []

        for i in range(0, len(cocktails)):
            self.t = cocktails[i][0]
            self.buttons.append("")
            self.buttons[i] = StdButton(text=self.t)
            self.buttons[i].bind(on_press=partial(self.switch, self.t))
            self.main_layout.add_widget(self.buttons[i])

    def switch(self, name, *args):
        pass
        global cur_cocktail
        for i in range(0,len(cocktails)):
            if cocktails[i][0] == name:
                cur_cocktail = i
                print(cocktails[i][0])
        self.manager.get_screen("display").define_layout()
        self.manager.current = 'display'

class DisplayScreen(Screen):

    def __init__(self, **kwargs):
        super(DisplayScreen, self).__init__(**kwargs)

    def define_layout(self):

        self.base_layout = VBoxLayout()
        self.add_widget(self.base_layout)

        self.header = TopRowBoxLayout()
        self.base_layout.add_widget(self.header)

        self.back_button = TopRowBackButton()
        self.back_button.bind(on_press=self.back_to_menu)
        self.header.add_widget(self.back_button)
        self.header.add_widget(TopRowFiller())

        self.scroll_area = StdScrollView()
        self.base_layout.add_widget(self.scroll_area)

        self.main_layout = MainLayout()
        self.scroll_area.add_widget(self.main_layout)

        self.main_layout.add_widget(Label(text=""))

        self.text = Label(text=cocktails[cur_cocktail][0], bold=True, underline=True, size_hint = (1, None), height=0.6*self.width)
        self.main_layout.add_widget(self.text)

    def back_to_menu(self,*args):
        self.manager.current = "choose"
        self.clear_widgets()


class CocktailApp(App):
    def build(self):
        self.sm = sm = ScreenManager()
        sm.add_widget(ChooseScreen(name='choose'))
        sm.add_widget(DisplayScreen(name='display'))

        return sm


if __name__ == '__main__':
    CocktailApp().run()

Let me know if some more preparation on the code has to be done.

EDIT 1

While the comment of John Anderson pointed out the problem, for some time I was still not able to figure out a solution. What I was missing is the precise definition of the binding of the MainLayout. The setter did the trick here.

class MainLayout(GridLayout):

    def __init__(self):
        super().__init__(cols=1, padding=(0.06*self.width, 0.06*self.width), spacing=0.02*self.width,
                     size_hint_y=None)
         self.bind(minimum_height=self.setter('height'))

Anyway, I still not understand the thing I described in problem 2, the shift of the labels. So I would still appreciate some further explanation here.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source