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

如何在Kivy循环中为所有MDCard添加Canvas阴影效果

解决Kivy循环创建MDCard时添加阴影的问题

问题分析

你当前代码的核心错误有三点:

  1. 创建MDCard时直接传入canvas=(...)的方式不符合Kivy的Canvas初始化规则
  2. 代码中self.pos、self.size、self.state的self指向的是ExcelApp实例,而非创建的MDCard,导致参数引用错误
  3. 未绑定卡片状态(normal/focus)变化事件,无法动态更新阴影模糊半径

下面提供两种可行的解决方案,优先推荐自定义卡片类的方式,更符合Kivy的组件化开发思路。


方案一:自定义带阴影的MDCard子类(推荐)

将阴影逻辑封装到自定义卡片类中,循环创建时直接实例化该类即可,代码复用性更高。

修改后的Python代码

from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel
from kivy.properties import NumericProperty

recent = [1, 2, 3, 4, 5]

# 自定义带阴影的MDCard
class ShadowMDCard(MDCard):
    normal_blur = NumericProperty(20)
    focus_blur = NumericProperty(10)

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        # 在canvas.before中添加阴影绘制指令
        with self.canvas.before:
            Color(rgba=(0, 0, 0, 0.8))
            self.shadow = BoxShadow(
                pos=self.pos,
                size=self.size,
                offset=(0, -5),
                spread_radius=(-10, -10),
                border_radius=(10, 10, 10, 10),
                blur_radius=self.normal_blur
            )
        # 绑定卡片位置、尺寸变化到阴影更新
        self.bind(pos=self._update_shadow_pos_size, size=self._update_shadow_pos_size)
        # 绑定卡片状态变化到阴影模糊半径更新
        self.bind(state=self._update_blur_radius)

    def _update_shadow_pos_size(self, instance, value):
        self.shadow.pos = self.pos
        self.shadow.size = self.size

    def _update_blur_radius(self, instance, value):
        self.shadow.blur_radius = self.normal_blur if value == 'normal' else self.focus_blur

class ExcelApp(MDApp):
    def build(self):
        self.theme_cls.theme_style = 'Light'
        self.theme_cls.material_style = 'M3'
        self.theme_cls.primary_palette = 'Blue'
        return Builder.load_file('Excel.kv')

    def on_start(self):
        for _ in recent:
            # 直接使用自定义的ShadowMDCard
            card = ShadowMDCard(
                MDBoxLayout(
                    MDLabel(
                        text='Name',
                        bold=True,
                        halign='center',
                    ),
                    MDBoxLayout(
                        MDLabel(
                            text='Box Type',
                            halign='center',
                            theme_text_color="Custom",
                            text_color='gray'
                        ),
                        MDLabel(
                            text='Box Type',
                            halign='center',
                            theme_text_color="Custom",
                            text_color='gray'
                        ),
                        MDLabel(
                            text='Box No.',
                            halign='center',
                            theme_text_color="Custom",
                            text_color='gray'
                        ),
                    ),
                    orientation='vertical'),
                size_hint=(1, .2),
                pos_hint={'center_x': .5, 'center_y': .5},
                focus_behavior=True,
                md_bg_color="#eeeeee",
                unfocus_color="#eeeeee",
                focus_color="#cccccc",
                ripple_behavior=True
            )
            self.root.ids.recent.add_widget(card)

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

方案二:循环创建时手动配置Canvas(无需自定义类)

如果不想创建子类,可以在每个MDCard实例化后,手动添加Canvas指令并绑定回调:

修改on_start方法代码

def on_start(self):
    for _ in recent:
        card = MDCard(
            MDBoxLayout(
                MDLabel(
                    text='Name',
                    bold=True,
                    halign='center',
                ),
                MDBoxLayout(
                    MDLabel(
                        text='Box Type',
                        halign='center',
                        theme_text_color="Custom",
                        text_color='gray'
                    ),
                    MDLabel(
                        text='Box Type',
                        halign='center',
                        theme_text_color="Custom",
                        text_color='gray'
                    ),
                    MDLabel(
                        text='Box No.',
                        halign='center',
                        theme_text_color="Custom",
                        text_color='gray'
                    ),
                ),
                orientation='vertical'),
            size_hint=(1, .2),
            pos_hint={'center_x': .5, 'center_y': .5},
            focus_behavior=True,
            md_bg_color="#eeeeee",
            unfocus_color="#eeeeee",
            focus_color="#cccccc",
            ripple_behavior=True
        )
        # 手动添加阴影
        with card.canvas.before:
            Color(rgba=(0, 0, 0, 0.8))
            shadow = BoxShadow(
                pos=card.pos,
                size=card.size,
                offset=(0, -5),
                spread_radius=(-10, -10),
                border_radius=(10, 10, 10, 10),
                blur_radius=20 if card.state == 'normal' else 10
            )
        # 绑定位置、尺寸更新回调
        def update_shadow(instance, value):
            shadow.pos = instance.pos
            shadow.size = instance.size
        card.bind(pos=update_shadow, size=update_shadow)
        # 绑定状态更新回调
        def update_blur(instance, value):
            shadow.blur_radius = 20 if value == 'normal' else 10
        card.bind(state=update_blur)
        
        self.root.ids.recent.add_widget(card)

关键注意事项

  • Canvas操作规则:必须通过with widget.canvas.before/canvas/canvas.after上下文添加绘图指令,不能在Widget初始化时直接传入canvas参数
  • 属性绑定:卡片的位置、尺寸、状态会动态变化,必须绑定这些属性到回调函数,才能让阴影实时跟随卡片变化
  • 自定义类优势:方案一的自定义类将阴影逻辑封装,避免重复代码,后续维护和扩展更方便

内容的提问来源于stack exchange,提问作者Tony Stark 001

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 00:38:10