Kivy多屏幕场景下自定义Button无响应问题求助
我看了你的代码,问题出在碰撞检测的坐标计算上!你在HomeScreenLogic的collide_point方法里用了Window.size来判断点击位置,但当这个部件被放到Screen里面后,它的坐标系统是相对于父容器(也就是HomeScreen)的,不再是全局的Window坐标,所以你的碰撞判断完全失效了,导致点击事件根本没被触发。
具体问题分析
你原来的collide_point里用了Window.size[0]/2和7*Window.size[1]/24来计算圆心,但HomeScreenLogic作为Screen的子部件,它的pos和size其实是和Screen一致的(因为你直接add_widget进去,没设置pos/size),所以应该用自身的self.pos和self.size来计算点击区域。
修复后的代码
这里是修改好的关键部分和完整代码:
首先修正HomeScreenLogic的collide_point方法:
class HomeScreenLogic(Widget, ButtonBehavior): def collide_point(self, x, y): # 转换为自身局部坐标,而不是Window全局坐标 local_x = x - self.pos[0] local_y = y - self.pos[1] # 用自身的size计算圆心位置 center_x = self.size[0]/2 center_y = self.size[1]/8 + self.size[0]/8 # 对应kv里的椭圆位置 radius = self.size[0]/8 if Vector(local_x, local_y).distance((center_x, center_y)) <= radius: print('点击命中!') return True return False # 可以加个测试的点击事件,确认能触发 def on_press(self): print('按钮被按下了!')
然后完整的代码:
from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.behaviors import ButtonBehavior from kivy.lang import Builder from kivy.vector import Vector from kivy.uix.screenmanager import ScreenManager, Screen Builder.load_string(""" <HomeScreenLogic>: canvas.before: Color: rgba: 1, 1, 1, 1 Rectangle: pos: self.pos size: self.size canvas: Color: rgba: ((25/255, 181/255, 254/255, 1) if self.state == "normal" else (137/255, 196/255, 244/255, 1)) Ellipse: size: root.width/4, root.width/4 pos: 0.5*root.width - root.width/8, root.height / 8 Color: rgba: 1, 1, 1, 1 Line: width: 2 points: [root.width/2-root.width/12, root.height/8+root.width/8,root.width/2+root.width/12, root.height/8+root.width/8] Color: rgba: 1, 1, 1, 1 Line: width: 2 points: [root.width/2, root.height/8+root.width/8+root.width/12,root.width/2, root.height/8+root.width/8-root.width/12] <AddScreen>: canvas.before: Color: rgba: 1, 1, 1, 1 Rectangle: pos: self.pos size: self.size """) class HomeScreenLogic(Widget, ButtonBehavior): def collide_point(self, x, y): # 转换为自身局部坐标 local_x = x - self.pos[0] local_y = y - self.pos[1] # 基于自身size计算圆心和半径 center_x = self.size[0]/2 center_y = self.size[1]/8 + self.size[0]/8 radius = self.size[0]/8 if Vector(local_x, local_y).distance((center_x, center_y)) <= radius: print('点击命中!') return True return False def on_press(self): print('按钮被按下了!') # 这里可以加你原来的按钮逻辑 class HomeScreen(Screen): def __init__(self, **kwargs): super(HomeScreen, self).__init__(**kwargs) self.Logic = HomeScreenLogic() self.add_widget(self.Logic) class AddScreen(Screen): pass SM = ScreenManager() SM.add_widget(HomeScreen(name='Home')) SM.add_widget(AddScreen(name='Add')) class HelloWorldApp(App): def build(self): return SM if __name__ == "__main__": HelloWorldApp().run()
额外建议
- 如果你不需要自定义Widget的复杂逻辑,其实可以直接让
HomeScreenLogic继承Button,这样不用自己写collide_point,Kivy会自动处理按钮的碰撞检测,更省心。 - 以后处理部件坐标时,一定要注意局部坐标和全局坐标的区别:
x, y参数是全局Window坐标,需要减去self.pos转换为部件自身的局部坐标再进行计算。
内容的提问来源于stack exchange,提问作者StevenD
相关产品推荐
相关产品推荐

