Search code examples
pythonkivyinfinite-scrollkivy-language

Create an infinite scrollview image in kivy


I'm trying to create a digital notebook app using kivy. I have my basic screen and a ScrollView of the lined sheet I want to use. It seems to be fine, but I now want this sheet to be infinite - meaning you'd be able to keep scrolling down and have more lines of it (like having the image duplicate itself vertically). How can I do that?

I'd really appreciate any help :)

Python code:

import kivy
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from PIL import Image as Image1
from kivy.uix.image import Image

GUI = Builder.load_file('style.kv')
img_size = Image1.open("images/notebook.png").size


class NotebookScreen(GridLayout):

    def __init__(self, **kwargs):
        self.rows = 1
        super(NotebookScreen, self).__init__(**kwargs)

    def get_size_for_notebook(self, **kwargs):
        global img_size
        width, height = Window.size
        return width, (img_size[0] * height / width)


class MainApp(App):

    def build(self):
        return NotebookScreen()


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

kv file:

<NotebookScreen>
    FloatLayout:
        rows: 2
        GridLayout:
            size_hint: 1, .05
            pos_hint: {"top": 1, "left": 1}
            id: tool_bar
            cols: 1
            canvas:
                Color:
                    rgba: 0, 0, 1, 1
                Rectangle:
                    pos: self.pos
                    size: self.size
        GridLayout:
            id: notebook_grid
            size_hint: 1, .95
            pos_hint: {"top": .95, "left": .97}
            cols: 1


            ScrollView:
                do_scroll: (False, True)  # up and down

                Image:
                    id: notebook_image
                    source: 'images/notebook.png'
                    allow_stretch: True
                    keep_ratio: False
                    pos: self.pos
                    size: root.get_size_for_notebook()
                    size_hint: 1, None

Solution

  • You can do that by using a BoxLayout to hold the lined sheet Image. Then you can just add more instances of the same Image to the BoxLayout as needed. Here is a version of your code that does that:

    from kivy.lang import Builder
    from kivy.app import App
    from kivy.properties import ObjectProperty
    from kivy.uix.gridlayout import GridLayout
    from kivy.uix.scrollview import ScrollView
    from kivy.core.window import Window
    from PIL import Image as Image1
    from kivy.uix.image import Image
    
    #GUI = Builder.load_file('style.kv')
    Builder.load_string('''
    <NotebookScreen>
        FloatLayout:
            GridLayout:
                size_hint: 1, .05
                pos_hint: {"top": 1, "left": 1}
                id: tool_bar
                cols: 1
                canvas:
                    Color:
                        rgba: 0, 0, 1, 1
                    Rectangle:
                        pos: self.pos
                        size: self.size
            GridLayout:
                id: notebook_grid
                size_hint: 1, .95
                pos_hint: {"top": .95, "left": 0}
                cols: 1
    
                MyScrollView:
                    do_scroll: (False, True)  # up and down
    
                    BoxLayout:
                        id: notebook_images
                        orientation: 'vertical'
                        size_hint: 1, None
                        height: self.minimum_height
                        MyImage:
    <MyImage>:
        source: 'images/notebook.png'
        allow_stretch: True
        keep_ratio: True
        size_hint: None, None
        size: self.get_size_for_notebook()
    ''')
    
    Window.size = (1000, 200)
    img_size =Image1.open("images/notebook.png").size
    
    
    class MyImage(Image):
        def get_size_for_notebook(self, **kwargs):
            global img_size
            width, height = Window.size
            return width, (img_size[0] * height / width)
    
    
    class MyScrollView(ScrollView):
        def on_scroll_y(self, instance, scroll_val):
            if scroll_val < 0.05:  # no logic for this number
                box = App.get_running_app().root.ids.notebook_images
                new_image = MyImage()
                box.add_widget(new_image)
                self.scroll_y = new_image.height / box.height  # a more careful calculation may provide smoother operation
    
    
    class NotebookScreen(GridLayout):
        def __init__(self, **kwargs):
            self.rows = 1
            super(NotebookScreen, self).__init__(**kwargs)
    
    
    class MainApp(App):
    
        def build(self):
            return NotebookScreen()
    
    
    if __name__ == "__main__":
        MainApp().run()
    

    The use of Builder.load_string() instead of load_file() is just for my own convenience.