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

Kivy Scatter组件绑定移动与旋转的实现难题

Kivy双Scatter组件同步移动与旋转的位置错乱修复方案

问题背景

现有代码实现了两个Scatter组件的拖拽同步,但需要扩展为位置与旋转完全同步。当前问题:Kivy默认以组件左下角为位置基准,旋转其中一个组件时,另一个会以自身左下角为轴旋转,在位置锁定逻辑下出现坐标错乱,无法实现完全同步。

原代码

from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder

class ScatterWidget(Scatter):
    pass

class SquareWidget(Widget):
    pass

class WindowManager(ScreenManager):
    pass

class GeneralWindow(Screen,RelativeLayout):

    start_position_1=(200,200)
    start_position_2=(300,400)
    def on_touch_move(self, touch):
        app=self.manager.get_screen('General')
        pos1=app.ids['widget10'].parent.to_parent(*self.pos)
        pos2=app.ids['widget20'].parent.to_parent(*self.pos)
        mov1=(pos1[0]-self.start_position_1[0],pos1[1]-self.start_position_1[1])
        mov2=(pos2[0]-self.start_position_2[0],pos2[1]-self.start_position_2[1])

        if self.ids.one.collide_point(*touch.pos):
            app.ids['two'].pos=(mov1[0]+self.start_position_2[0],mov1[1]+self.start_position_2[1])
        if self.ids.two.collide_point(*touch.pos):
            app.ids['one'].pos =(mov2[0]+self.start_position_1[0],mov2[1]+self.start_position_1[1])

KV = Builder.load_string("""

WindowManager:
    GeneralWindow:

<GeneralWindow>:
    name: 'General'
    RelativeLayout:
        canvas: 
            Color: 
                rgba: 0,0,0,0.3
            Rectangle: 
                size: self.size
                pos: self.pos
        
        ScatterWidget:
            do_scale: False
            id: one
            size_hint: None,None
            size: widget10.size
            rotation: 0
            pos: root.start_position_1

            SquareWidget:
                id: widget10
                size: (200,20)
                canvas: 
                    Color:
                        rgba: 1,1,0,0.7
                    Rectangle:
                        size: self.size
                        pos:  self.pos  

        ScatterWidget:
            do_scale: False
            id: two
            size_hint: None,None
            size: widget20.size
            rotation: 0
            pos: root.start_position_2

            SquareWidget:
                id: widget20
                size: (200,20)
                canvas: 
                    Color:
                        rgba: 0,1,0,0.7
                    Rectangle:
                        size: self.size
                        pos:  self.pos 

""")

class TApp(App):

    def build(self):
        return KV

if __name__ == '__main__':
    TApp().run()

修复方案

核心思路是同步两个Scatter的完整变换矩阵,而不是单独同步pos和rotation——因为Scatter的旋转会改变自身局部坐标到父坐标的映射,手动计算位置会忽略这一变换。具体实现:

  1. 修改ScatterWidget,在变换发生时触发同步逻辑
  2. 在GeneralWindow中通过组件ID匹配目标同步对象
  3. 移除原有的on_touch_move手动位置同步逻辑,改用变换矩阵复制实现精准同步

修改后的完整代码

from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.graphics.transformation import Matrix

class ScatterWidget(Scatter):
    def on_transform_with_touch(self, touch):
        # 执行原Scatter的触摸变换逻辑
        super().on_transform_with_touch(touch)
        # 获取父容器GeneralWindow实例
        parent_screen = self.parent.parent
        # 根据当前组件ID确定需要同步的目标组件
        target_scatter = parent_screen.ids.two if self.id == 'one' else parent_screen.ids.one
        
        # 重置目标组件的变换,避免叠加旧变换
        target_scatter.apply_transform(Matrix().translate(0,0,0))
        # 复制当前组件的完整变换矩阵(包含平移、旋转)
        target_scatter.apply_transform(self.transform)

class SquareWidget(Widget):
    pass

class WindowManager(ScreenManager):
    pass

class GeneralWindow(Screen,RelativeLayout):
    start_position_1=(200,200)
    start_position_2=(300,400)

KV = Builder.load_string("""

WindowManager:
    GeneralWindow:

<GeneralWindow>:
    name: 'General'
    RelativeLayout:
        canvas: 
            Color: 
                rgba: 0,0,0,0.3
            Rectangle: 
                size: self.size
                pos: self.pos
        
        ScatterWidget:
            do_scale: False
            id: one
            size_hint: None,None
            size: widget10.size
            rotation: 0
            pos: root.start_position_1

            SquareWidget:
                id: widget10
                size: (200,20)
                canvas: 
                    Color:
                        rgba: 1,1,0,0.7
                    Rectangle:
                        size: self.size
                        pos:  self.pos  

        ScatterWidget:
            do_scale: False
            id: two
            size_hint: None,None
            size: widget20.size
            rotation: 0
            pos: root.start_position_2

            SquareWidget:
                id: widget20
                size: (200,20)
                canvas: 
                    Color:
                        rgba: 0,1,0,0.7
                    Rectangle:
                        size: self.size
                        pos:  self.pos 

""")

class TApp(App):
    def build(self):
        return KV

if __name__ == '__main__':
    TApp().run()

关键说明

  • on_transform_with_touch是Scatter处理触摸变换(移动、旋转)的核心方法,在此处同步能保证变换实时生效
  • transform属性包含了Scatter的完整变换信息(平移、旋转),直接复制该矩阵可确保两个组件视觉状态完全一致
  • 先重置目标组件的变换矩阵,避免多次叠加变换导致的位置偏移

内容的提问来源于stack exchange,提问作者BH10001

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 13:55:36