如何实现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会在旋转后的坐标系中生效,导致视觉上宽高同时变化。要实现目标效果,需做以下调整:
- 固定锚点在右下角;
- 根据当前旋转角度,选择动画
height或width(旋转90度后,原高度对应旋转后的宽度); - 动画过程中实时调整组件位置,确保右下角锚点的全局坐标不变。
修改后的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
相关产品推荐
相关产品推荐

