Problem with Kivy - Scrollview overlapping with Bo

2019-08-18 00:27发布

问题:

I'm clicking the button in the fixed menu at the top even though there is a Button in the vertical Scrollview, it doesn't effect the fixed menu.

I'm trying to click at the button in the fixed menu at the top but the actions are being sent to the horizontal Scrollview below.

I'm new with kivy and even programming and I'm creating an app with Python 3.7, and Kivy(1.11.1) that kinda looks like Netflix's Page layout, in which there is a menu fixed at the top and below it there is a vertical Scrollview with some horizontal scrollviews inside (just like the Netlix's catalogues, eg.: My List, Continue Watching, Series, etc). The problem is that when I scroll down the vertical ScrollView and one of the horizontal scrollview gets behind the fixed menu at the top, the events are sent to this horizontal scrollview and not to the fixed menu that is be above the Scrollview content. If I scroll the vertical Scrollview down and one of the horizontal scrollviews gets "behind" (in quotes because I imagine they aren't supposed to collide with each other) the menu at the top, if I click in the menu the event is being sent to the horizontal scrollview.

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView

Builder.load_string(
'''
<ScrollApp>:
    orientation: 'vertical'
    BoxLayout:
        size_hint: 1, 0.12
        Button:
            text: 'Fixed Menu'
            on_press: print('This button stops working if there is a horizontal scrollview "behind"')

    ScrollView:
        bar_width: 10
        scroll_type: ['bars', 'content']
        BoxLayout:
            orientation: 'vertical'
            size_hint: 1, None
            height: self.minimum_height
            padding: 22, 0, 22, 50
            spacing: 50
            canvas:
                Color:
                    rgba: .15, .15, .15, .9
                Rectangle:
                    size: self.size
                    pos: self.pos
            Button:
                size_hint: None, None
                width: 100
                height: 100
                on_press: print('This button does not overlap the menu above')

            # "ScrollViews containers"
            Custom
            Custom
            Custom
            Custom
            Custom
    BoxLayout:  
        size_hint: 1, 0.12
        Button:
            on_press: print("This menu at the bottom is not affected by the problem that occurs with the top one")


<Custom@BoxLayout>:
    orientation: 'vertical'
    size_hint: 1, None
    height: self.minimum_height
    Label:
        size_hint: None, None
        size: self.texture_size
        id: label
        font_size: 20
        text: 'Teste'
    ScrollView:
        do_scroll: True, True
        size_hint: 1, None
        height: 150
        GridLayout:
            id: grid
            size_hint: None, 1.01
            width: self.minimum_width
            spacing: 5
            cols: 3
            Button:
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
            Button:
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
            Button:
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
''' )

class ScrollApp(BoxLayout):
    pass

class Test(App):
    def build(self):
        return ScrollApp()

Test().run()


回答1:

You do manage to find obscure situations in kivy :-).

I believe the problem here is the order of touch event processing. The touch events are normally processed by the top level widgets, and they dispatch the event to their children, all the way down the Widget tree. In any Widget with multiple children, the touch event is dispatched to children in the reverse order that they have been add to their parent. And in kv, child Widgets are added in order as they appear (top to bottom). So, in your case, the ScrollApp has three children, and the touch events will be dispatched to its children starting with the BoxLayout that contains the bottom menu, then to the ScrollView, and lastly, to the BoxLayout that contains the top menu. So the ScrollView gets the touch event before the top menu, and dispatches the touch to its children (including the Custom instances). So the Buttons in the Custom see the touch event before the top menu sees it, and they claim it as their own. The obvious way to fix it would be to change the order of Widgets that appear in the ScrollApp, but since ScrollApp is a BoxLayout, that would dramatically change how your App looks. A different fix is to use a Layout that doesn't depend on the order of its children. Something like a FloatLayout has that characteristic. So, here is a modified version of your code, that makes ScrollApp extend FloatLayout instead of BoxLayout:

from kivy.app import App
from kivy.lang.builder import Builder
from kivy.uix.floatlayout import FloatLayout

Builder.load_string(
'''
<ScrollApp>:
    ScrollView:
        size_hint: 1.0, 0.76
        pos_hint: {'center_y':0.5}
        bar_width: 10
        scroll_type: ['bars', 'content']
        BoxLayout:
            orientation: 'vertical'
            size_hint: 1, None
            height: self.minimum_height
            padding: 22, 0, 22, 50
            spacing: 50
            canvas:
                Color:
                    rgba: .15, .15, .15, .9
                Rectangle:
                    size: self.size
                    pos: self.pos
            Button:
                size_hint: None, None
                width: 100
                height: 100
                on_press: print('This button does not overlap the menu above')

            # "ScrollViews containers"
            Custom
            Custom
            Custom
            Custom
            Custom
    BoxLayout:  
        size_hint: 1, 0.12
        pos_hint: {'y':0}
        Button:
            on_press: print("This menu at the bottom is not affected by the problem that occurs with the top one")
    BoxLayout:
        size_hint: 1, 0.12
        pos_hint: {'top':1.0}
        Button:
            text: 'Fixed Menu'
            on_press: print('This button stops working if there is a horizontal scrollview "behind"')


<Custom@BoxLayout>:
    orientation: 'vertical'
    size_hint: 1, None
    height: self.minimum_height
    Label:
        size_hint: None, None
        size: self.texture_size
        id: label
        font_size: 20
        text: 'Teste'
    ScrollView:
        do_scroll: True, True
        size_hint: 1, None
        height: 150
        GridLayout:
            id: grid
            size_hint: None, 1.01
            width: self.minimum_width
            spacing: 5
            cols: 3
            Button:
                text: 'button 1'
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
            Button:
                text: 'button 2'
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
            Button:
                text: 'button 3'
                size_hint: None, None
                size: 400, 150
                on_press: print('ScrollView button pressed')
''' )


class ScrollApp(FloatLayout):
    pass


class Test(App):
    def build(self):
        return ScrollApp()

Test().run()

So, with this child order in ScrollApp, the top and bottom menus get their shot at the touch events before the ScrollView.

I actually think your code should work without this change, so there may be something else happening that I couldn't find.