Kivy:如何将背景触摸与小部件触摸分开?

时间:2015-04-15 02:44:03

标签: python kivy

在我的应用中,我想分别处理背景触摸和小部件触摸。 Widget documentation忽略了如何防止来自.kv事件的冒泡。这是一个小测试案例:

from kivy.app import App

class TestApp(App):

  def on_background_touch(self):
    print("Background Touched")
    return True

  def on_button_touch(self):
    print("Button Touched")

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

和.kv:

#:kivy 1.8.0

BoxLayout:
  orientation: "vertical"
  on_touch_down: app.on_background_touch()
  padding: 50, 50

  Button:
    text: "Touch me!"
    on_touch_down: app.on_button_touch()

结果:触摸背景或按钮会触发两个处理程序。我应该执行碰撞检测,还是有其他方式?

3 个答案:

答案 0 :(得分:3)

您应该执行碰撞检测。例如,在类定义中:

class YourWidget(SomeWidget):
    def on_touch_down(self, touch):
        if self.collide_point(*touch.pos):
            do_stuff()

编辑:实际上,您的方法无论如何都无法正常工作,因为Button与BoxLayout重叠。我可能会改为创建一个BoxLayout子类并覆盖on_touch_down,先调用super,然后再调用False(表示尚未使用触摸)进行BoxLayout交互。

答案 1 :(得分:1)

我想要一个允许我绑定.kv个文件的事件的解决方案。 @inclement解决方案不允许我这样做,因为一旦您从.kv绑定事件,您就不能再返回True来告诉您处理该事件的父级:

Button:
  # you can't return True here, neither from the handler itself
  on_touch_down: app.button_touched()

所以我所做的就是在父母身上执行碰撞检测,只有当它没有击中任何孩子时才会发出自定义on_really_touch_down,并且再次对孩子进行碰撞检测,因为所有孩子都会收到无论如何都是触摸(这是一团糟,我知道)。这是完整的解决方案(需要 Kivy> = 1.9.0 ,因为使用walk方法):

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout

class CustomTouchMixin(object):

  def __init__(self, *args, **kwargs):
    super(CustomTouchMixin, self).__init__(*args, **kwargs)
    self.register_event_type("on_really_touch_down")

  def on_really_touch_down(self, touch):
    pass

class CustomTouchWidgetMixin(CustomTouchMixin):

  def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):
      self.dispatch("on_really_touch_down", touch)
    return super(CustomTouchWidgetMixin, self).on_touch_down(touch)

class CustomTouchLayoutMixin(CustomTouchMixin):

  def on_touch_down(self, touch):
    for child in self.walk():
      if child is self: continue
      if child.collide_point(*touch.pos):
        # let the touch propagate to children
        return super(CustomTouchLayoutMixin, self).on_touch_down(touch)
    else:
      super(CustomTouchLayoutMixin, self).dispatch("on_really_touch_down", touch)
      return True

class TouchHandlerBoxLayout(CustomTouchLayoutMixin, BoxLayout):
  pass

class TouchAwareButton(CustomTouchWidgetMixin, Button):
  pass

class TestApp(App):

  def on_background_touch(self):
    print("Background Touched")

  def on_button_touch(self, button_text):
    print("'{}' Touched".format(button_text))

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

.kv

#:kivy 1.9.0

TouchHandlerBoxLayout:

  padding: 50, 50
  on_really_touch_down: app.on_background_touch()

  TouchAwareButton:
    text: "Button One"
    on_really_touch_down: app.on_button_touch(self.text)

  TouchAwareButton:
    text: "Button Two"
    on_really_touch_down: app.on_button_touch(self.text)

因此,这允许我绑定来自.kv的触摸。

答案 2 :(得分:0)

通过.kv文件/字符串语法绑定触摸事件的方法是可行的,这里是一个在检测到碰撞时修改调用者背景的示例。

<cLabel@Label>:
    padding: 5, 10
    default_background_color: 0, 0, 0, 0
    selected_background_color: 0, 1, 0, 1
    on_touch_down:
        ## First & second arguments passed when touches happen
        caller = args[0]
        touch = args[1]
        ## True or False for collisions & caller state
        caller_touched = caller.collide_point(*touch.pos)
        background_defaulted = caller.background_color == caller.default_background_color
        ## Modify caller state if touched
        if caller_touched and background_defaulted: caller.background_color = self.selected_background_color
        elif caller_touched and not background_defaulted: caller.background_color = caller.default_background_color

    background_color: 0, 0, 0, 0
    canvas.before:
        Color:
            rgba: self.background_color
        Rectangle:
            pos: self.pos
            size: self.size

为了完整起见,以下是如果在触摸激活的布局中如何使用上述代码,只有在没有孩子(或孙子孙女等)也与同一事件发生冲突时才会触发。

<cGrid@GridLayout>:
    on_touch_down:
        caller = args[0]
        touch = args[1]
        caller_touched = caller.collide_point(*touch.pos)
        spawn_touched = [x.collide_point(*touch.pos) for x in self.walk(restrict = True) if x is not self]
        ## Do stuff if touched and none of the spawn have been touched
        if caller_touched and True not in spawn_touched: print('caller -> {0}\ntouch -> {1}'.format(caller, touch))
    cols: 2
    size_hint_y: None
    height: sorted([x.height + x.padding[1] for x in self.children])[-1]
    cLabel:
        text: 'Foo'
        size_hint_y: None
        height: self.texture_size[1]
    cLabel:
        text: 'Bar'
        size_hint_y: None
        height: self.texture_size[1] * 2
  

我可能已经texture_size向后,或者可能没有,但是高度欺骗在大多数情况下可以被忽略,因为它的目的是帮助制作父布局更可点击。

     

来电者的变色和打印触摸对象应该用do_stuff()或类似方法替换,因为它们会使示例自包含,并显示处理调用者保存状态的另一种方式。