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

如何实现MDLabel异轴旋转与动画?旋转后高度动画异常排查

问题描述

我用ButtonBehavior、RotateBehavior和MDLabel实现了一个可点击旋转的标签组件:

  • 当rotate_value_angle设为0时,能正常将MDLabel的高度从500动画至250;
  • 将角度改为90度后,标签可按预期逆时针旋转,但此时动画MDLabel时,高度与宽度会同时变化。

我需要实现的效果是:保持锚点在右下角,仅让原高度对应的左侧部分从左向右移动,效果等同于把手机逆时针倾斜90度后rotate_value_angle为0时的动画状态。

原代码

Kivy代码

MDScreen:
    RotateLabel:
        id: label
        size_hint: None, None
        size: 300, 500
        pos: 210, 470
        text: "Hello World!"
        on_release: app.change_rotate(self)
        md_bg_color: "red"

Python代码

from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivy.uix.behaviors import ButtonBehavior, RotateBehavior
from kivy.lang import Builder
from kivy.animation import Animation

kv = '''
MDScreen:
    RotateLabel:
        id: label
        size_hint: None, None
        size: 300, 500
        pos: 210, 470
        text: "Hello World!"
        on_release: app.change_rotate(self)
        md_bg_color: "red"
'''

class RotateLabel(ButtonBehavior, RotateBehavior, MDLabel):
    pass

class Test(MDApp):
    def build(self):
        return Builder.load_string(kv)

    def change_rotate(self, instance_button: RotateLabel) -> None:
        anim = Animation(
            rotate_value_angle = 90 if instance_button.rotate_value_angle == 0 else 0, 
            d = 1)
        
        anim.bind(on_start = self.change_label_height)
        anim.start(instance_button)

    def change_label_height(self, *args):
        label = self.root.ids.label
        if label.size == [300, 500]:
            anim = Animation(height = 250, d = 2)
            anim.start(label)
        else:
            anim = Animation(height = 500, d = 2)
            anim.start(label)

Test().run()
解决方案

问题根源是RotateBehavior旋转后,组件的局部坐标系会同步旋转,此时直接动画height会在旋转后的坐标系中生效,导致视觉上宽高同时变化。要实现目标效果,需做以下调整:

  1. 固定锚点在右下角;
  2. 根据当前旋转角度,选择动画height或width(旋转90度后,原高度对应旋转后的宽度);
  3. 动画过程中实时调整组件位置,确保右下角锚点的全局坐标不变。

修改后的Kivy代码

MDScreen:
    RotateLabel:
        id: label
        size_hint: None, None
        size: 300, 500
        pos: 210, 470
        text: "Hello World!"
        on_release: app.change_rotate(self)
        md_bg_color: "red"
        anchor: (1, 0)  # 锚点设为右下角

修改后的Python代码

from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivy.uix.behaviors import ButtonBehavior, RotateBehavior
from kivy.lang import Builder
from kivy.animation import Animation

kv = '''
MDScreen:
    RotateLabel:
        id: label
        size_hint: None, None
        size: 300, 500
        pos: 210, 470
        text: "Hello World!"
        on_release: app.change_rotate(self)
        md_bg_color: "red"
        anchor: (1, 0)
'''

class RotateLabel(ButtonBehavior, RotateBehavior, MDLabel):
    pass

class Test(MDApp):
    def build(self):
        return Builder.load_string(kv)

    def change_rotate(self, instance_button: RotateLabel) -> None:
        target_angle = 90 if instance_button.rotate_value_angle == 0 else 0
        anim = Animation(rotate_value_angle=target_angle, d=1)
        anim.bind(on_start=self.change_label_dimension)
        anim.start(instance_button)

    def change_label_dimension(self, *args):
        label = self.root.ids.label
        current_angle = label.rotate_value_angle
        # 记录右下角锚点的全局坐标,确保动画时位置不变
        anchor_global_x, anchor_global_y = label.to_window(label.width, 0)

        if current_angle == 0:
            # 未旋转时,动画高度并锁定右下角位置
            if label.height == 500:
                anim = Animation(height=250, d=2)
                anim.bind(on_progress=lambda _, widget, p: setattr(widget, 'y', anchor_global_y - widget.height))
                anim.start(label)
            else:
                anim = Animation(height=500, d=2)
                anim.bind(on_progress=lambda _, widget, p: setattr(widget, 'y', anchor_global_y - widget.height))
                anim.start(label)
        else:
            # 旋转90度后,动画宽度(对应原高度)并锁定右下角位置
            if label.width == 300:
                # 原高度500对应旋转后的初始宽度,目标宽度250对应原高度250
                anim = Animation(width=250, d=2)
                anim.bind(on_progress=lambda _, widget, p: setattr(widget, 'x', anchor_global_x - widget.width))
                anim.start(label)
            else:
                anim = Animation(width=500, d=2)
                anim.bind(on_progress=lambda _, widget, p: setattr(widget, 'x', anchor_global_x - widget.width))
                anim.start(label)

Test().run()
关键说明
  • 锚点固定:anchor=(1,0)将旋转锚点锁定在组件右下角,旋转时组件围绕该点转动;
  • 坐标系适配:旋转90度后,组件局部坐标系逆时针旋转,原高度对应旋转后的宽度,因此需动画width而非height;
  • 位置锁定:通过on_progress回调实时调整组件的x/y坐标,确保右下角锚点的全局位置始终不变,实现“仅原高度对应部分移动”的效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 09:53:17