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

使用KivyMD框架MDHero组件时触发KeyError及动画异常求助

KivyMD Hero动画失效及KeyError问题修复

问题现象

  • 清空并重新填充Screen A的GridLayout后,所有Hero移动相关动画完全失效
  • 新增Hero并点击后,Screen B无法显示对应图片,返回Screen A时触发KeyError异常
  • 检查Screen A子组件可找到带对应tag的MDHeroFrom,但问题无法解决

问题根源

  1. 重复tag冲突:新增的Hero统一使用'New obj'作为name,导致多个Hero组件的tag完全相同,KivyMD的Hero动画系统无法正确匹配对应组件
  2. 图片源未同步:切换到Screen B时,MDHeroTo组件未同步源Hero的图片地址,导致显示空白
  3. 返回逻辑依赖失效:返回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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 21:27:02