You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 04:56:44