i everyone, I'm struggling with a touch propagation issue in Kivy. I have a main vertical ScrollView containing several horizontal RecycleViews (categories, features, etc.) and a final vertical RecycleView for a "popular" list. The issue: > When I

34 views
Skip to first unread message

Mk2 on the beatz

unread,
Mar 23, 2026, 11:14:01 AM (11 days ago) Mar 23
to Kivy users support

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.clock import Clock

class CardHorizontal(BoxLayout):
    title = StringProperty('')

class CardVertical(BoxLayout):
    title = StringProperty('')

KV = '''
<CardHorizontal>:
    orientation: 'vertical'
    size_hint_x: None
    width: 140
    padding: 10
    canvas.before:
        Color:
            rgba: 0.2, 0.2, 0.2, 1
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [10]
    Label:
        text: root.title

<CardVertical>:
    size_hint_y: None
    height: 80
    padding: 10
    canvas.before:
        Color:
            rgba: 0.15, 0.15, 0.15, 1
        Rectangle:
            size: self.size
            pos: self.pos
    Label:
        text: root.title

<HorizontalRV@RecycleView>:
    do_scroll_x: True
    do_scroll_y: False
    size_hint_y: None
    height: 150
    viewclass: 'CardHorizontal'
    scroll_timeout: 100
    RecycleBoxLayout:
        default_size: None, None
        default_size_hint: None, 1
        size_hint_x: None
        width: self.minimum_width
        orientation: 'horizontal'
        spacing: 10
        padding: 10

BoxLayout:
    orientation: 'vertical'
    canvas.before:
        Color:
            rgba: 0.05, 0.05, 0.05, 1
        Rectangle:
            size: self.size
            pos: self.pos

    Label:
        text: "TOUT EST VISIBLE ICI"
        size_hint_y: None
        height: 50
        bold: True

    # --- LE SCROLL PRINCIPAL ---
    ScrollView:
        id: main_scroll
        on_scroll_y: app.check_infinite_scroll(self)

        BoxLayout:
            orientation: 'vertical'
            size_hint_y: None
            height: self.minimum_height
            spacing: 20
            padding: 10

            # 1. Categories
            Label:
                text: "Catégories"
                size_hint_y: None
                height: 30
            HorizontalRV:
                id: rv_cats
                height: 60

            # 2. Features
            Label:
                text: "Features"
                size_hint_y: None
                height: 30
            HorizontalRV:
                id: rv_features
                height: 180

            # 3. Recommandés
            Label:
                text: "Recommandés"
                size_hint_y: None
                height: 30
            HorizontalRV:
                id: rv_reco
                height: 120

            # 4. Populaires (RECYCLEVIEW)
            Label:
                text: "Populaires (Infini)"
                size_hint_y: None
                height: 30
           
            RecycleView:
                id: list_populaire
                viewclass: 'CardVertical'
                size_hint_y: None
                # TRÈS IMPORTANT : On lie la hauteur à son contenu interne
                height: rv_layout.minimum_height
                do_scroll_y: False # On laisse le ScrollView principal scroller
               
                RecycleBoxLayout:
                    id: rv_layout
                    default_size: None, 80
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    spacing: 5
'''

class TestApp(App):
    def build(self):
        return Builder.load_string(KV)

    def on_start(self):
        # Données horizontales
        self.root.ids.rv_cats.data = [{'title': f'Cat {i}'} for i in range(15)]
        self.root.ids.rv_features.data = [{'title': f'Feature {i}'} for i in range(10)]
        self.root.ids.rv_reco.data = [{'title': f'Reco {i}'} for i in range(10)]

        # Données verticales
        self.load_more_items()

    def load_more_items(self, *args):
        rv = self.root.ids.list_populaire
        current_data = rv.data
        new_items = [{'title': f"Populaire #{len(current_data) + i}"} for i in range(15)]
        rv.data.extend(new_items)
        rv.refresh_from_data()

    def check_infinite_scroll(self, scrollview):
        # On vérifie le scroll du ScrollView principal
        if scrollview.scroll_y <= 0.05:
            Clock.schedule_once(self.load_more_items, 0.2)

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

ElliotG

unread,
Mar 23, 2026, 11:22:18 AM (11 days ago) Mar 23
to Kivy users support
Unfortunately Nested ScrollView does not work on V2.3.1.  There is an updated on the master branch that does support nested scrolling.  https://github.com/kivy/kivy/blob/master/kivy/uix/scrollview.py

You can download the file and replace the current file in your venv.  It should be something like:
.venv/Lib/site-packages/kivy/uix/scrollview.py

Tomek CEDRO

unread,
Mar 23, 2026, 3:39:30 PM (11 days ago) Mar 23
to kivy-...@googlegroups.com
time for 2.3.2 release? :-)

--
CeDeROM, SQ7MHZ, http://www.tomek.cedro.info

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/kivy-users/ca253230-4093-49e3-8994-159372d69b27n%40googlegroups.com.

elli...@cox.net

unread,
Mar 23, 2026, 8:02:05 PM (11 days ago) Mar 23
to kivy-...@googlegroups.com
I had a brief exchange with a maintainer on the Kivy Discord about the next release.  This was in regard to a different issue.  His response, "I don't think there will be another version of Kivy 2"

The master branch has already been moved over to SDL3 (from SDL2).  

From: kivy-...@googlegroups.com <kivy-...@googlegroups.com> on behalf of Tomek CEDRO <to...@cedro.info>
Sent: Monday, March 23, 2026 12:39 PM
To: kivy-...@googlegroups.com <kivy-...@googlegroups.com>
Subject: Re: [kivy-users] Re: i everyone, I'm struggling with a touch propagation issue in Kivy. I have a main vertical ScrollView containing several horizontal RecycleViews (categories, features, etc.) and a final vertical RecycleView for a "popular" list. The...
 

Tomek CEDRO

unread,
Mar 23, 2026, 8:18:01 PM (11 days ago) Mar 23
to kivy-...@googlegroups.com
Message has been deleted

pedro h

unread,
Mar 25, 2026, 3:23:57 AM (10 days ago) Mar 25
to Kivy users support

Hey deloinfi,

Give this code a try! I hope it helps with your scrolling issue.

It uses a FloatLayout as the root to separate the header from the main list, which avoids the touch propagation conflicts you usually get with nested ScrollViews in V2.3.1.

Best regards, Pedro




from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout

from kivy.properties import StringProperty
from kivy.clock import Clock

class MainRoot(FloatLayout):
    def on_touch_move(self, touch):
        rv, header = self.ids.list_populaire, self.ids.header_area
        if header.collide_point(*touch.pos) and abs(touch.dy) > abs(touch.dx):
            rv.scroll_y = max(0, min(1, rv.scroll_y - (touch.dy / 500)))
            return True
        return super().on_touch_move(touch)


class CardHorizontal(BoxLayout):
    title = StringProperty('')

class CardVertical(BoxLayout):
    title = StringProperty('')

KV = '''
<CardHorizontal>:
    orientation: 'vertical'
    size_hint_x: None
    width: 140
    padding: 10
    canvas.before:
        Color:
            rgba: 0.2, 0.2, 0.2, 1
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [10]
    Label:
        text: root.title
        input_transparent: True


<CardVertical>:
    size_hint_y: None
    height: 80
    padding: 10
    canvas.before:
        Color:
            rgba: 0.15, 0.15, 0.15, 1
        Rectangle:
            size: self.size
            pos: self.pos
    Label:
        text: root.title

MainRoot:

    canvas.before:
        Color:
            rgba: 0.05, 0.05, 0.05, 1
        Rectangle:
            size: self.size
            pos: self.pos
    RecycleView:
        id: list_populaire
        viewclass: 'CardVertical'
        on_scroll_y: app.on_scroll(self)

        RecycleBoxLayout:
            id: rv_layout
            default_size: None, 80
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
            spacing: 5
            padding: [10, 590, 10, 10]
    BoxLayout:
        id: header_area
        orientation: 'vertical'
        size_hint: 1, None
        height: 500
        y: root.height - self.height - 80
        spacing: 10
        padding: 10
        canvas.before:
            Color:

                rgba: 0.05, 0.05, 0.05, 1
            Rectangle:
                size: self.size
                pos: self.pos
        Label:
            text: "Categories"

            size_hint_y: None
            height: 30
        RecycleView:
            id: rv_cats
            viewclass: 'CardHorizontal'
            size_hint_y: None
            height: 40
            do_scroll_x: True
            do_scroll_y: False

            RecycleBoxLayout:
                default_size: None, None
                default_size_hint: None, 1
                size_hint_x: None
                width: self.minimum_width
                orientation: 'horizontal'
                spacing: 10
        Label:
            text: "Features"
            size_hint_y: None
            height: 30
        RecycleView:
            id: rv_features
            viewclass: 'CardHorizontal'
            size_hint_y: None
            height: 160
            do_scroll_x: True
            do_scroll_y: False

            RecycleBoxLayout:
                default_size: None, None
                default_size_hint: None, 1
                size_hint_x: None
                width: self.minimum_width
                orientation: 'horizontal'
                spacing: 10
        Label:
            text: "Recommended"

            size_hint_y: None
            height: 30
        RecycleView:
            id: rv_reco
            viewclass: 'CardHorizontal'
            size_hint_y: None
            height: 100
            do_scroll_x: True
            do_scroll_y: False

            RecycleBoxLayout:
                default_size: None, None
                default_size_hint: None, 1
                size_hint_x: None
                width: self.minimum_width
                orientation: 'horizontal'
                spacing: 10
        Label:
            text: "Popular (Infinite)"
            size_hint_y: None
            height: 30
            bold: True

    Label:
        text: "TOUT EST VISIBLE ICI"
        size_hint: 1, None
        height: 50
        pos_hint: {'top': 1}
        bold: True
        canvas.before:
            Color:
                rgba: 0.08, 0.08, 0.08, 1

            Rectangle:
                size: self.size
                pos: self.pos
'''

class TestApp(App):
    is_loading = False

    def build(self): return Builder.load_string(KV)
    def on_start(self):
        self.root.ids.rv_cats.data = [{'title': f'Cat {i}'} for i in range(15)]
        self.root.ids.rv_features.data = [{'title': f'Feature {i}'} for i in range(10)]
        self.root.ids.rv_reco.data = [{'title': f'Reco {i}'} for i in range(10)]
        self.load_more_items()
    def on_scroll(self, rv):
        if rv.scroll_y <= 0.1 and not self.is_loading:
            self.is_loading = True
            self.load_more_items()
        header = self.root.ids.header_area
        scroll_px = (1.0 - rv.scroll_y) * (rv.children[0].height - rv.height)
        header.y = self.root.height - 500 - 80 + min(500, scroll_px)

    def load_more_items(self, *args):
        rv = self.root.ids.list_populaire
        rv.data.extend([{'title': f"Popular #{len(rv.data) + i}"} for i in range(15)])
        rv.refresh_from_data()
        Clock.schedule_once(lambda dt: setattr(self, 'is_loading', False), 0.5)


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



Reply all
Reply to author
Forward
0 new messages