Kivy中RecycleView添加DownloadItem实例出现重复、消失异常问题
问题根因
你遇到的条目重复异常是Kivy RecycleView的视图复用机制导致的:
- RecycleView不会为每一条数据创建独立的DownloadItem实例,只会创建刚好铺满显示区域的少量实例,滚动时通过将新数据绑定到旧视图实例的方式复用控件,降低内存占用
- 你当前代码里的
paused、fill_animation是DownloadItem的普通实例属性,且对按钮图标、进度条状态的修改没有同步到RecycleView的data数据源中,视图复用时旧视图的残留状态就会被带到新数据的展示上,看起来就像条目被复制了。
修复方案
需要把所有动态状态都和数据源绑定,所有视图状态的变化都通过修改数据源触发,修改后的可运行代码如下:
from kivy.lang import Builder from kivy.properties import StringProperty, ColorProperty, BooleanProperty, NumericProperty from kivy.animation import Animation from kivymd.uix.card import MDCard from kivymd.app import MDApp class DownloadItem(MDCard): path = StringProperty() url_type = StringProperty("file") paused = BooleanProperty(False) progress_value = NumericProperty(50) progress_color = ColorProperty() # 记录当前项在数据源中的索引 item_index = NumericProperty(-1) def __init__(self, **kwargs): self.fill_animation = None super(DownloadItem, self).__init__(**kwargs) def pause_resume_download(self): # 先获取当前RecycleView的数据源 rv = self.parent.parent if self.item_index < 0 or self.item_index >= len(rv.data): return # 修改数据源属性,自动触发视图更新 item_data = rv.data[self.item_index] if not self.paused: item_data["paused"] = True if self.fill_animation is not None: self.fill_animation.cancel(self.ids.progress_bar) item_data["progress_color"] = app.pause_color else: item_data["paused"] = False self.fill_animation = Animation(value=100, duration=4) def on_anim_complete(*args): if self.item_index < len(rv.data): rv.data[self.item_index]["progress_color"] = self.theme_cls.accent_color self.fill_animation.bind(on_complete=on_anim_complete) self.fill_animation.start(self.ids.progress_bar) item_data["progress_color"] = self.theme_cls.primary_color # 刷新数据源触发重绘 rv.data[self.item_index] = item_data def cancel_download(self): rv = self.parent.parent if self.item_index < 0 or self.item_index >= len(rv.data): return if self.fill_animation is not None: self.fill_animation.cancel(self.ids.progress_bar) # 修改数据源 item_data = rv.data[self.item_index] item_data["progress_color"] = app.fail_color rv.data[self.item_index] = item_data class Example(MDApp): fail_color = ColorProperty([255 / 255, 99 / 255, 71 / 255, 1.0]) pause_color = ColorProperty([240 / 255, 163 / 255, 10 / 255, 1.0]) success_color = ColorProperty(None) color_duration = .15 def __init__(self, **kwargs): global app super(Example, self).__init__(**kwargs) self.kv = Builder.load_string(''' #:kivy 2.0.0 <TooltipMDLabel@MDLabel+MDTooltip> <DownloadItem>: orientation: "vertical" padding: 10 spacing: 10 size_hint_y: None height: 100 elevation: 20 border_radius: 5 radius: [5] MDBoxLayout: adaptive_height: True spacing: 5 MDIcon: icon: root.url_type size_hint_x: None width: self.texture_size[0] TooltipMDLabel: text: root.path if len(root.path) <= 30 else root.path[:31] + " ..." tooltip_text: f"Path: {root.path} | Type: {root.url_type}" tooltip_bg_color: app.theme_cls.bg_darkest tooltip_text_color: app.theme_cls.opposite_bg_darkest size_hint_y: None height: self.texture_size[1] MDSeparator: MDBoxLayout: spacing: 10 MDProgressBar: id: progress_bar min: 0 max: 100 value: root.progress_value color: root.progress_color MDIconButton: id: pause_resume_button icon: "play" if root.paused else "pause" pos_hint: {"center_x": .5, "center_y": .5} on_release: root.pause_resume_download() MDIconButton: icon: "close" pos_hint: {"center_x": .5, "center_y": .5} on_release: root.cancel_download() BoxLayout: orientation: "vertical" spacing: 10 RecycleView: id: downloads_list viewclass: "DownloadItem" RecycleBoxLayout: default_size: None, 100 default_size_hint: 1, None size_hint_y: None height: self.minimum_height orientation: "vertical" padding: 15 spacing: 15 MDRaisedButton: text: "Add" size_hint_x: 1 on_release: app.add_item()''') self.path = "C:/Users/Family/Downloads/" app = self def build(self): self.theme_cls.theme_style = "Dark" return self.kv def add_item(self): # 新增条目时把所有动态属性都加入数据源 self.kv.ids.downloads_list.data.append({ "path": self.path + '/' if self.path[-1] != '/' else self.path, "url_type": "file", "paused": False, "progress_value": 50, "progress_color": self.theme_cls.primary_color, "item_index": len(self.kv.ids.downloads_list.data) }) if __name__ == '__main__': Example().run()
内容的提问来源于stack exchange,提问作者Omid Ketabollahi
相关产品推荐
相关产品推荐

