如何在Kivy循环中为所有MDCard添加Canvas阴影效果
解决Kivy循环创建MDCard时添加阴影的问题
问题分析
你当前代码的核心错误有三点:
- 创建
MDCard时直接传入canvas=(...)的方式不符合Kivy的Canvas初始化规则 - 代码中
self.pos、self.size、self.state的self指向的是ExcelApp实例,而非创建的MDCard,导致参数引用错误 - 未绑定卡片状态(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
相关产品推荐
相关产品推荐

