如何在Kivy(Python代码)中实现窗口resize时子部件位置更新
问题:Kivy子Widget窗口调整大小时无法更新位置
我有一个父Widget负责添加并定位自定义Widget对象,但当窗口调整大小时,这些子部件无法更新自身位置。以下是我的实现代码:
import kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.graphics import Color, Line, Ellipse, Rectangle from kivy.metrics import dp from kivy.uix.widget import Widget class circleChild(Widget): def __init__(self, *args, **kwargs): self.pos = kwargs.get('_pos') self.size = kwargs.get('_size') super(circleChild, self).__init__() with self.canvas: Color(0.4, 0.6, 0.9, 1) # ConrflowerBlue Ellipse(pos=self.pos, size=self.size, width=dp(2)) class RootWidget(BoxLayout): def __init__(self, *args, **kwargs): BoxLayout.__init__(self, *args, **kwargs) self.bind(pos=self.draw) self.bind(size=self.draw) self.layout1 = BoxLayout() self.layout2 = BoxLayout(opacity=0.8) self.add_widget(self.layout1) self.add_widget(self.layout2) self.childrenAdded = False def draw(self, *args): with self.canvas.before: Color(.8, .8, .8, 1) # LightGrey self.bg = Rectangle(pos=self.pos, size=self.size) self.layout1.canvas.clear() with self.layout1.canvas: Color(0, 0, 0, 1) # Black Line( points=[ self.center_x, self.center_y - 200, self.center_x, self.center_y + 200 ], width=dp(2)) Line( points=[ self.center_x - 200, self.center_y, self.center_x + 200, self.center_y ], width=dp(2)) if not self.childrenAdded: self.addChildren() def addChildren(self, *args): circle1 = circleChild( _pos=[self.center_x + 100, self.center_y + 100], _size=[100, 100]) circle2 = circleChild( _pos=[self.center_x + 100, self.center_y - 200], _size=[100, 100]) circle3 = circleChild( _pos=[self.center_x - 200, self.center_y + 100], _size=[100, 100]) circle4 = circleChild( _pos=[self.center_x - 200, self.center_y - 200], _size=[100, 100]) self.layout2.add_widget(circle1) self.layout2.add_widget(circle2) self.layout2.add_widget(circle3) self.layout2.add_widget(circle4) self.childrenAdded = True class PositionChildren(App): title = "PositionChildren" def build(self): return RootWidget() if __name__ == "__main__": PositionChildren().run()
现在的问题是调整窗口大小时,子部件的位置不会随之更新,请问如何使用Python代码重新定位这些子Widget?
解决方案
咱们先拆解下问题出在哪:你现在的子Widget circleChild 只在初始化时设置了一次位置,而且椭圆的绘制参数直接固定死了,窗口resize时完全没触发更新逻辑。另外,你把这些子Widget加到了 BoxLayout 里,BoxLayout 本身会自动管理子部件的布局,这反而会干扰你手动定位的操作——这可是个隐形的坑,得先把这个布局换成普通的 Widget,这样我们才能完全手动控制位置。
下面是修改后的完整代码,我会一步步解释关键改动:
import kivy from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.graphics import Color, Line, Ellipse, Rectangle from kivy.metrics import dp from kivy.uix.widget import Widget class CircleChild(Widget): def __init__(self, offset_x, offset_y, size, *args, **kwargs): # 存储相对于父中心的偏移量,而非绝对位置 self.offset_x = offset_x self.offset_y = offset_y self.circle_size = size super().__init__(*args, **kwargs) def update_position(self, parent_center_x, parent_center_y): # 根据父容器的新中心重新计算自身位置(让圆心对齐偏移后的点) self.pos = (parent_center_x + self.offset_x - self.circle_size[0]/2, parent_center_y + self.offset_y - self.circle_size[1]/2) # 清空画布后重新绘制椭圆,确保位置同步更新 self.canvas.clear() with self.canvas: Color(0.4, 0.6, 0.9, 1) # CornflowerBlue Ellipse(pos=self.pos, size=self.circle_size) class RootWidget(BoxLayout): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.bind(pos=self.update_all) self.bind(size=self.update_all) self.layout1 = BoxLayout() # 把layout2换成普通Widget,避免BoxLayout自动布局干扰手动定位 self.layout2 = Widget(opacity=0.8) self.add_widget(self.layout1) self.add_widget(self.layout2) self.circles = [] self.add_children() def update_all(self, *args): # 更新背景和十字线 with self.canvas.before: Color(.8, .8, .8, 1) # LightGrey self.bg = Rectangle(pos=self.pos, size=self.size) self.layout1.canvas.clear() with self.layout1.canvas: Color(0, 0, 0, 1) # Black Line(points=[self.center_x, self.center_y - 200, self.center_x, self.center_y + 200], width=dp(2)) Line(points=[self.center_x - 200, self.center_y, self.center_x + 200, self.center_y], width=dp(2)) # 窗口变化时,遍历所有子Circle更新位置 for circle in self.circles: circle.update_position(self.center_x, self.center_y) def add_children(self, *args): # 用偏移量而非绝对位置创建子Circle,保证相对位置逻辑统一 circle1 = CircleChild(offset_x=100, offset_y=100, size=(100,100)) circle2 = CircleChild(offset_x=100, offset_y=-200, size=(100,100)) circle3 = CircleChild(offset_x=-200, offset_y=100, size=(100,100)) circle4 = CircleChild(offset_x=-200, offset_y=-200, size=(100,100)) self.circles.extend([circle1, circle2, circle3, circle4]) for circle in self.circles: self.layout2.add_widget(circle) # 初始化时先执行一次更新,确保初始位置正确 self.update_all() class PositionChildren(App): title = "PositionChildren" def build(self): return RootWidget() if __name__ == "__main__": PositionChildren().run()
关键改动说明:
- 存储偏移量而非绝对位置:子Circle不再保存初始化时的固定位置,而是记录相对于父容器中心的偏移值,这样窗口resize时只需要根据新的父中心重新计算位置即可。
- 添加
update_position方法:负责根据父容器的新中心更新自身位置,同时清空画布重绘椭圆——因为之前的椭圆参数是固定的,必须重绘才能同步新位置。 - 替换
layout2为普通Widget:原来的BoxLayout会自动调整子部件的位置和大小,完全破坏手动定位逻辑,换成普通Widget后就能完全自主控制了。 - 统一更新逻辑:在
update_all方法中,每次窗口的位置或大小变化时,除了更新背景和十字线,还会遍历所有子Circle调用update_position方法,实现批量更新。 - 保存子部件引用:用
self.circles列表存储所有子Circle的引用,方便后续更新时快速找到它们。
这样修改后,调整窗口大小的时候,四个蓝色的圆就会跟着父容器的中心一起移动,始终保持相对位置不变啦。
内容的提问来源于stack exchange,提问作者Keno
相关产品推荐
相关产品推荐

