如何让自定义RoundedButton仅绑定自身,点击时不触发其他按钮动画?
调试时发现按钮传递的self对应的my_id是正确的,但点击“Option A”时三个按钮都会播放动画,点击“Option B”时B和C会播放动画。为什么同一页面中同属RoundedButton类的其他按钮会被on_touch_down事件触发?(这些按钮位于屏幕管理器中,相关代码未展示)
相关代码
1. RoundedButton的KV定义
<RoundedButton@Button>: style: "elevated" background_color: (0, 0, 0, 0) background_normal: "" background_down: "" canvas.before: Color: rgba: (0.72, 0.68, 0.92, 1) if self.state == 'normal' else (0.33, 0.48, 0.92, 1) RoundedRectangle: size: self.size pos: self.pos radius: [20] Color: rgba: (0.72, 0.68, 0.92, 1) Line: rounded_rectangle: (self.pos[0], self.pos[1], self.size[0], self.size[1], 20) width: 1 if self.state == 'normal' else 5
2. 布局中的按钮代码
MDBoxLayout: orientation: 'horizontal' RoundedButton: my_id: 'optionA' text: 'Option A' size_hint: 0.15, 0.35 on_touch_down: app.animate_darken(self) RoundedButton: my_id: 'optionB' text: 'Option B' size_hint: 0.15, 0.35 on_touch_down: app.animate_darken(self) RoundedButton: my_id: 'optionC' text: 'Option C' size_hint: 0.15, 0.35 on_touch_down: app.animate_darken(self)
3. animate_darken函数代码
def animate_darken(self, widget, fade_length=0.5, *args): print(widget.my_id) BG_color = widget.background_color A = Animation( background_color=(0,0,0,1), duration=fade_length #seconds ) A += Animation(x=0, y=0, duration=0.3) A += Animation(background_color=BG_color) # reset it A.start(widget)
问题原因
- 触摸事件传递机制:Kivy中
on_touch_down事件会沿Widget树上下传递,默认不拦截的话,同级或父级Widget都会收到该事件,导致多个按钮触发回调。 - 共享属性实例:
background_color默认是普通列表,所有RoundedButton实例共享同一个列表对象,动画修改时会影响所有使用该默认值的按钮。
解决方法
步骤1:让每个按钮拥有独立的背景色属性
修改RoundedButton的KV定义,将background_color改为ListProperty,确保每个按钮实例的背景色是独立对象:
<RoundedButton@Button>: style: "elevated" background_color: ListProperty([0, 0, 0, 0]) # 替换为ListProperty background_normal: "" background_down: "" canvas.before: Color: rgba: (0.72, 0.68, 0.92, 1) if self.state == 'normal' else (0.33, 0.48, 0.92, 1) RoundedRectangle: size: self.size pos: self.pos radius: [20] Color: rgba: (0.72, 0.68, 0.92, 1) Line: rounded_rectangle: (self.pos[0], self.pos[1], self.size[0], self.size[1], 20) width: 1 if self.state == 'normal' else 5
步骤2:拦截触摸事件,避免传递给其他按钮
在RoundedButton类中添加on_touch_down方法,仅当触摸点在当前按钮范围内时触发动画,并阻止事件继续传递:
<RoundedButton@Button>: # 保留上述已修改的属性和canvas代码 def on_touch_down(self, touch): if self.collide_point(*touch.pos): app.animate_darken(self) return True # 返回True阻止事件传递给其他Widget return super().on_touch_down(touch)
步骤3:移除布局中的重复事件绑定
修改布局代码,去掉每个按钮的on_touch_down绑定,因为已经在类内部处理:
MDBoxLayout: orientation: 'horizontal' RoundedButton: my_id: 'optionA' text: 'Option A' size_hint: 0.15, 0.35 RoundedButton: my_id: 'optionB' text: 'Option B' size_hint: 0.15, 0.35 RoundedButton: my_id: 'optionC' text: 'Option C' size_hint: 0.15, 0.35
可选优化:修正动画中的位置偏移
原动画中的Animation(x=0, y=0)会将按钮移到屏幕左上角,若不是预期效果,可删除该动画段,或改为基于按钮当前位置的相对动画:
def animate_darken(self, widget, fade_length=0.5, *args): print(widget.my_id) BG_color = widget.background_color.copy() # 复制当前颜色,避免引用问题 A = Animation( background_color=(0,0,0,1), duration=fade_length ) # 移除或修改位置动画 # A += Animation(x=0, y=0, duration=0.3) A += Animation(background_color=BG_color) A.start(widget)
内容的提问来源于stack exchange,提问作者Marc Maxmeister
相关产品推荐
相关产品推荐

