使用KivyMD框架MDHero组件时触发KeyError及动画异常求助
KivyMD Hero动画失效及KeyError问题修复
问题现象
- 清空并重新填充Screen A的GridLayout后,所有Hero移动相关动画完全失效
- 新增Hero并点击后,Screen B无法显示对应图片,返回Screen A时触发KeyError异常
- 检查Screen A子组件可找到带对应tag的MDHeroFrom,但问题无法解决
问题根源
- 重复tag冲突:新增的Hero统一使用
'New obj'作为name,导致多个Hero组件的tag完全相同,KivyMD的Hero动画系统无法正确匹配对应组件 - 图片源未同步:切换到Screen B时,MDHeroTo组件未同步源Hero的图片地址,导致显示空白
- 返回逻辑依赖失效:返回Screen A时依赖
hero_to.tag,但tag冲突或组件引用丢失会触发KeyError
修复方案
1. 生成唯一的Hero tag
修改HeroApp的add方法,给每个新增Hero分配唯一名称:
def add(self, btn=None): global i i += 1 hero_data.append({'name': f'New obj {i}', 'address': 'Someone'}) self.clear() self.show()
2. 同步Screen B的HeroTo图片源
更新ScreenB的set_atr_obj方法,将源Hero的图片地址传递给MDHeroTo:
def set_atr_obj(self, id_obj, hero): self.id_obj = id_obj self.hero_from = hero # 同步图片源到HeroTo组件 self.hero_to.source = hero.tile.source
3. 优化Hero切换逻辑
移除Card中不必要的Clock延迟调度,直接完成状态设置与屏幕切换:
def on_release(self): """Switch screen and post data""" scr_b = self.scr_manager.scr_b scr_b.set_atr_obj(self.tag, self) self.scr_manager.current_heroes = [self.tag] scr_b.hero_to.tag = self.tag self.scr_manager.current = "scr_b"
4. 修复返回时的tag引用
修改ScreenB的go_back方法,直接使用保存的源Hero tag,避免依赖可能失效的hero_to.tag:
def go_back(self): """Returns to the page with the list of houses""" self.manager.current_heroes = [self.hero_from.tag] self.manager.current = "scr_a"
修改后的完整代码
Python文件
from kivymd.uix.hero import MDHeroFrom from kivymd.uix.screen import MDScreen from kivymd.uix.screenmanager import MDScreenManager from kivymd.app import MDApp from kivy.animation import Animation from kivy.properties import ObjectProperty, DictProperty hero_data = [ {'name': 'obj1', 'address': 'aa'}, {'name': 'obj2', 'address': 'bb'}, {'name': 'obj3', 'address': 'cc'}, ] i = 3 class Card(MDHeroFrom): info = DictProperty() scr_manager = ObjectProperty() def __init__(self, **kwargs): super().__init__(**kwargs) self.tag = self.info['name'] self.tile.ids.image.ripple_duration_in_fast = 0.05 def on_transform_in(self, instance_hero_widget, duration): Animation( radius=[0, 0, 0, 0], box_radius=[0, 0, 0, 0], duration=duration, ).start(instance_hero_widget) def on_transform_out(self, instance_hero_widget, duration): Animation( radius=[24, 24, 24, 24], box_radius=[0, 0, 24, 24], duration=duration, ).start(instance_hero_widget) def on_release(self): """Switch screen and post data""" scr_b = self.scr_manager.scr_b scr_b.set_atr_obj(self.tag, self) self.scr_manager.current_heroes = [self.tag] scr_b.hero_to.tag = self.tag self.scr_manager.current = "scr_b" class ScreenB(MDScreen): name = "scr_b" def set_atr_obj(self, id_obj, hero): """Set attribute with id opening obj and remeber hero.""" self.id_obj = id_obj self.hero_from = hero # 同步图片源 self.hero_to.source = hero.tile.source def go_back(self): """Returns to the page with the list of houses""" self.manager.current_heroes = [self.hero_from.tag] self.manager.current = "scr_a" class ScreenA(MDScreen): box = ObjectProperty() name = 'scr_a' class Container(MDScreenManager): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.scr_a = ScreenA() self.scr_b = ScreenB() self.add_widget(self.scr_a) self.add_widget(self.scr_b) class HeroApp(MDApp): def __init__(self, **kwargs): self.data = { "Add new": [ 'plus', 'on_release', self.add ], "Clear": [ 'delete-outline', 'on_release', self.clear ], "Show": [ 'eye-outline', 'on_release', self.show ] } super().__init__(**kwargs) def build(self): self.theme_cls.theme_style = "Dark" self.container = Container() return self.container def on_start(self): self.show() def add(self, btn=None): global i i += 1 hero_data.append({'name': f'New obj {i}', 'address': 'Someone'}) self.clear() self.show() def clear(self, btn=None): self.container.scr_a.box.clear_widgets() def show(self, btn=None): box = self.container.scr_a.box for hero in hero_data: box.add_widget(Card(info= hero, scr_manager= self.container)) if __name__ == "__main__": HeroApp().run()
KV文件
<Card> padding: 4 size_hint: 0.5, None size_y: dp(200) radius: 24 label_item: label_item tile: tile MDSmartTile: id: tile radius: 24 box_radius: 0, 0, 24, 24 box_color: 0, 0, 0, .5 source: "testdata/house.jpg" size_hint: None, None size: root.size mipmap: True lines: 2 on_release: root.on_release() TwoLineListItem: id: label_item text: f"[b]{root.info['name']}[/b]" secondary_text: root.info['address'] pos_hint: {"center_y": .5} _no_ripple_effect: True <ScreenB> heroes_to: [hero_to] hero_to: hero_to MDBoxLayout: orientation: "vertical" MDHeroTo: id: hero_to size_hint: 1, None height: dp(220) pos_hint: {"top": 1} # 确保HeroTo支持图片显示 source: "" MDLabel: text: 'Someone widgets' MDIconButton: icon: "arrow-left" pos_hint: {"top": 1, "right": .12} on_release: root.go_back() <ScreenA> box: box MDGridLayout: id: box cols: 2 MDFloatingActionButtonSpeedDial: data: app.data
内容的提问来源于stack exchange,提问作者Tokar
相关产品推荐
相关产品推荐

