Skip to content
Advertisement

Kivy Scrollview with FloatLayout Is Positioning itself at the bottom of the screen

In summary, the program takes a bunch of list data and populates a row to describe a task with some actions attached to it.

After much work I got scrollview working with Floatlayout somewhat. The issue is that the list of rows of labels are stuck at the bottom part of the screen as in the screenshot. When you run the code, put your curser towards the very bottom of the screen and scrolldown. The widgets will appear and it scrolls (yay). You will then have experienced my two problems

  1. Why do the widgets start outside of view
  2. Why is the layout stuck at the bottom.

I am looking at how to make that floatlayout be 12/15 from the top of the screen, and for the widgets to be in frame from start.

enter image description here

import kivy
kivy.require('2.1.0') # replace with your current kivy version !

from kivy.config import Config
Config.set('kivy', 'keyboard_mode', 'systemanddock')

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty, ObjectProperty
from kivy.factory import Factory
from kivy.uix.scrollview import ScrollView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout

from kivy.core.window import Window

from os import path, getcwd
from datetime import date, timedelta, datetime
from calendar import monthrange
from dateutil.parser import parse
from dateutil.relativedelta import relativedelta

class MainScreen(Screen):

    headings = ['Bucket', 'Description', 'Due Date', 'Priority', 'Repeat', 'Person', 'Complete']
    lst = [['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None']]
               #all
    lst_widgets = []

    filtercomplete = 0

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

    def on_enter(self):
        self.InitialiseWidgets()    
    # Draw all widgets on screen
    def InitialiseWidgets(self):
        self.clear_widgets()
        self.WidgetScroller()
        self.WidgetTable()

    def WidgetScroller(self):

        scrlv = ScrollView(size_hint=(1,None), do_scroll_x = False)
        self.layout = FloatLayout(size_hint=(1, None), size=(Window.width, Window.height))
        scrlv.add_widget(self.layout)
        self.add_widget(scrlv)

    def WidgetTable(self):
        num_headings = len(self.headings)
        row_data = []
        self.tabledata = []
        y_pos = 12/15
        if len(self.lst) != 0: # if list is not empty         
            for x in range(len(self.lst)): # for each task in list               
                x_pos = 0/10                
                for y in range(num_headings): # for each property of task                    
                    if y == self.headings.index('Complete'):
                        if self.filtercomplete == 1: # if filtering by completed, 
                            row_data.append(Label(text = " ", size_hint = (1/20, 1/15), pos_hint = {'x':8/10, 'top':y_pos})) # do not add finish button
                        else:
                            row_data.append(Button(text = "FINISH", size_hint = (1/20, 1/15), pos_hint = {'x':8/10, 'top':y_pos})) # else add finish button                    
                    else:
                        print(self.lst[x][y])
                        row_data.append(Label(text = self.lst[x][y], size_hint = (1/10, 1/15), pos_hint = {'x':x_pos, 'top':y_pos})) # create label for task properties                        
                    if y == 1:
                        x_pos = x_pos + 3/10
                    else:
                        x_pos = x_pos + 1/10

                row_data.append(Button(text = "PUSH", size_hint = (1/20, 1/15), pos_hint = {'x':8.5/10, 'top':y_pos})) # Action buttons
                row_data.append(Button(text = "EDIT", size_hint = (1/20, 1/15), pos_hint = {'x':9/10, 'top':y_pos}))
                row_data.append(Button(text = "DEL", size_hint = (1/20, 1/15), pos_hint = {'x':9.5/10, 'top':y_pos}))

                y_pos = y_pos - 1/15

                self.tabledata.append(row_data.copy())
                row_data *= 0             
        # Draw widgets
        for widget_row in self.tabledata:
            for widget in widget_row:
                self.layout.add_widget(widget)

class MyApp(App):

    def build(self):
        # Create the screen manager
        sm = ScreenManager()
        sm.add_widget(MainScreen(name='main'))
        return sm

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

Advertisement

Answer

Your questions:

  1. The widgets are outside the initial view because you are positioning the widgets using pos_hint of {'y': 12/15} to start. That sets the largest y pos_hint at 0.8, and you decrease it from there. So the layout above 0.8 is empty. Also, your ScrollView has size_hint set to None for y. That causes the default value of 100 to be used as its height, so only the top 100 pixels of the layout can fit.
  2. The default pos for wigets is [0,0]. Since you don’t set the position of the ScrollView, it will get the default pos.

I suggest using a Layout that will do a lot of the work for you. Perhaps a GridLayout. And you can define the look of the MainScreen using kv. Here is a kv that does that:

<MainScreen>:
    ScrollView:
        do_scroll_x: False
        pos_hint: {'top': 0.75}
        size_hint: 1, 0.5
        GridLayout:
            id: grid
            cols: 10
            size_hint_y: None  # required to allow automatice size adjustment
            row_default_height: 50  # each row in the GridLayout will be given at least 50 pixels
            height: self.minimum_height  # the GridLayout will adjust its size as needed

If you load the above kv before creating the MainScreen(), then you can simplify your py code for the MainScreen class:

class MainScreen(Screen):

    headings = ['Bucket', 'Description', 'Due Date', 'Priority', 'Repeat', 'Person', 'Complete']
    lst = [['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None'],
           ['None','','29-May-2022','Low','None','None']]
               #all
    lst_widgets = []

    filtercomplete = 0

    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)
        self.widgets_drawn = False

    def on_enter(self):
        if not self.widgets_drawn:
            self.InitialiseWidgets()
            self.widgets_drawn = True

    # Draw all widgets on screen
    def InitialiseWidgets(self):
        # self.clear_widgets()
        # self.WidgetScroller()
        self.WidgetTable()

    # def WidgetScroller(self):
    #
    #     self.scrlv = ScrollView(size_hint=(1,None), do_scroll_x = False, pos_hint={'top': 0.75})
    #     self.layout = FloatLayout(size_hint=(1, None), size=(Window.width, Window.height))
    #     self.scrlv.add_widget(self.layout)
    #     self.add_widget(self.scrlv)

    def WidgetTable(self):
        self.layout = self.ids.grid  # this is the GridLayout
        num_headings = len(self.headings)
        row_data = []
        self.tabledata = []
        if len(self.lst) != 0: # if list is not empty
            for x in range(len(self.lst)): # for each task in list
                for y in range(num_headings): # for each property of task
                    if y == self.headings.index('Complete'):
                        if self.filtercomplete == 1: # if filtering by completed,
                            row_data.append(Label(text = " ", size_hint = (1/20, None))) # do not add finish button
                        else:
                            row_data.append(Button(text = "FINISH", size_hint = (1/20, None))) # else add finish button
                    else:
                        print(self.lst[x][y])
                        row_data.append(Label(text = self.lst[x][y], size_hint = (1/10, None))) # create label for task properties

                row_data.append(Button(text = "PUSH", size_hint = (1/20, None)))  # Action buttons
                row_data.append(Button(text = "EDIT", size_hint = (1/20, None)))
                row_data.append(Button(text = "DEL", size_hint = (1/20, None)))
                self.tabledata.append(row_data.copy())
                row_data *= 0
        # Draw widgets
        for widget_row in self.tabledata:
            for widget in widget_row:
                self.layout.add_widget(widget)

Note that all the position information is removed from the created Buttons and Labels, since the GridLayout handles that.

Another thing to note is that you are using on_enter() to trigger the widget drawing. That will get triggered every time you enter the MainScreen, so I have added a widgets_drawn attribute to avoid adding the same widgets every time the MainScreen is entered.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement