Kivy RecycleView-图标已复制并放置在数据项中

问题描述

我正在尝试基于KivyMD FileManager类构建Gallery应用程序。布局由三列组成,图像放置在这些列中。

问题是,当我单击图像(它们是IconButtons)以在图像的左下角添加小点击图标时,在同一列中的其他图像上又添加了一些单击图标。我认为是因为布局在几个周期后重复了相同的实例。

Click icon added as the button (image) pressed
Unexpected click icon appears in another row (third row after the clicked row)

伪代码如下:


import os
import threading
import time

from os import listdir
from os.path import join,isfile
from pathlib import Path

import PIL
from PIL import ImageOps
from kivy import Logger
from kivy.app import App
from kivy.clock import mainthread
from kivy.lang import Builder
from kivy.metrics import dp
from kivy.properties import OptionProperty,ListProperty,BooleanProperty,StringProperty
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import AsyncImage
from kivy.uix.recycleview import RecycleView
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.uix.scrollview import ScrollView
from kivymd import images_path
from kivymd.app import MDApp
from kivymd.toast import toast
from PIL import Image as PILImage
from kivymd.uix.button import MDIconButton
from kivymd.uix.label import MDIcon
from kivymd.uix.toolbar import MDToolbar

Builder.load_string('''
#:import os os
<MyToolBar>:
    id: toolbar
    elevation: 10
    pos_hint:{'top':1}
    size_hint_y: 0.1
    md_bg_color: 0/255,176/255,240/255,1
    specific_text_color: 1,1,1
<RV>:
    id: rv
    key_viewclass: 'viewclass'
    key_size: 'height'
    bar_width: dp(4)
    bar_color: app.theme_cls.primary_color
    #on_scroll_stop: root._update_list_images()
    pos_hint: {'top':0.9}
    size_hint_y: 0.9
    
    RecycleBoxLayout:
        default_size: None,dp(500)
        default_size_hint: 1,None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
        
<LabelContent@MDLabel>
    size_hint_y: None
    height: self.texture_size[1]
    shorten: True
    shorten_from: 'center'
    halign: 'center'
    text_size: self.width,None
    
<BodyManagerWithPrevious>
    id: bodymanager
    paths: []
    path: ''
    type: 'folder'
    events_callback: lambda x: None
    orientation: 'vertical'
    
    MDGridLayout:
        id: grid_box
        cols: 3
        row_default_height: (self.width - self.cols * self.spacing[0]) / self.cols
        row_force_default: True
        adaptive_height: True
        padding: dp(4),dp(-4)
        spacing: dp(4),dp(4)
        #pos_hint: {'top':1}

        BoxLayout:
            orientation: 'vertical'
            IconButton:
                size_hint_y: None
                height: dp(300) if self.source and os.path.split(self.source)[1] == "folder.png" else root.width /3
                source: root.get_source(root.type,root.paths,1)
                on_release: root.events_callback(path=root.get_source(root.type,1),instance=self)
            MDIcon:
                icon: ''
                pos: self.parent.children[1].pos

        BoxLayout:
            orientation: 'vertical'
            IconButton:
                size_hint_y: None
                height: dp(300) if self.source and os.path.split(self.source)[1] == "folder.png" else root.width /3
                source: root.get_source(root.type,2)
                on_release: root.events_callback(path=root.get_source(root.type,2),instance=self)
            MDIcon:
                icon: ''
                pos: self.parent.children[1].pos
        BoxLayout:
            orientation: 'vertical'
            IconButton:
                size_hint_y: None
                height: dp(300) if self.source and os.path.split(self.source)[1] == "folder.png" else root.width /3
                source: root.get_source(root.type,3)
                on_release: root.events_callback(path=root.get_source(root.type,3),instance=self)
            MDIcon:
                icon: ''
                pos: self.parent.children[1].pos
''')


class IconButton(ButtonBehavior,AsyncImage):
    allow_stretch = BooleanProperty()
    clicked = BooleanProperty()


class MyToolBar(MDToolbar):
    pass


class RV(RecycleView):
    search = OptionProperty("all",options=["all","files"])
    ext = ListProperty()
    use_access = BooleanProperty(True)

    def __init__(self,path,**kwargs):
        super(RV,self).__init__(**kwargs)

        self.ext = [".png",".jpg",".jpeg"]
        self.app = MDApp.get_running_app()

        dirs,files = self.get_content(path)

        threading.Thread(target=self._create_previous,args=(path,)).start()

        split_files = self._split_list(files,3)

        manager_list = []
        app = MDApp.get_running_app()
        for list_files in list(split_files):
            manager_list.append(
                {
                    "viewclass": "BodyManagerWithPrevious","path": path,"paths": list_files,"type": "files","height": app.root.width / 3,"events_callback": app.add_checkicon
                })
        self.data = manager_list
        #[{'source': x} for x in img_source]

    def count_ext(self,path):
        ext = os.path.splitext(path)[1]
        if ext != "":
            # print(self.ext)
            if ext.lower() in self.ext or ext.upper() in self.ext:
                return True
        return False

    def _create_previous(self,path):
        if "r" not in self.get_access_string(path):
            toast("PermissionError")
            return

        for image in os.listdir(path):
            _path = os.path.join(path,image)
            if os.path.isfile(_path):
                if self.count_ext(_path):
                    path_to_thumb = os.path.join(
                        '/home/username/Desktop',"thumb",f"thumb_{image}"
                    )
                    if not os.path.exists(path_to_thumb):
                        im = PILImage.open(_path)
                        im = ImageOps.fit(im,(200,200),method=0,bleed=0.0,centering=(0.5,0.5))
                        im.thumbnail((200,200))
                        im.save(path_to_thumb,"PNG")

    def get_access_string(self,path):
        access_string = ""
        if self.use_access:
            access_data = {"r": os.R_OK,"w": os.W_OK,"x": os.X_OK}
            for access in access_data.keys():
                access_string += (
                    access if os.access(path,access_data[access]) else "-"
                )
        return access_string

    def get_content(self,path):
        """Returns a list of the type [[Folder List],[file list]]."""
        print(path)
        try:
            files = []
            dirs = []
            onlyfiles1 = [join(path,f) for f in listdir(path) if isfile(join(path,f))
                          and f.endswith(('JPG','png',".jpeg"))]
            print(onlyfiles1)
            onlyfiles1.sort(key=os.path.getmtime)
            onlyfiles1.reverse()
            print(onlyfiles1)
            for each in onlyfiles1:
                content = each.split('/')[-1]
                if os.path.isdir(os.path.join(path,content)):
                    if self.search == "all" or self.search == "dirs":
                        dirs.append(content)
                else:
                    if self.search == "all" or self.search == "files":
                        if len(self.ext) != 0:
                            try:
                                if self.count_ext(content):
                                    files.append(
                                        os.path.join(
                                            '/home/username/Desktop',f"thumb_{content}",)
                                    )

                            except IndexError:
                                pass
                        else:
                            files.append(content)
            return dirs,files
        except OSError:
            self.history.pop()
            return None,None

    def _update_list_images(self):
        # self.refresh_from_viewport()
        self.refresh_from_layout()

    def _split_list(self,l,n):
        if l:
            n = max(1,n)
            return (l[i : i + n] for i in range(0,len(l),n))
        else:
            return []


class BodyManagerWithPrevious(BoxLayout):
    def get_source(self,source_type,paths,index):
        if len(paths) >= index:
            source = paths[index - 1]
        else:
            source = f"{images_path}transparent.png"
        return source


class TestApp(MDApp):
    check_dict = {}
    iconcuk = StringProperty()
    instance_nums = []

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(Screen(name='Screen 1'))
        self.sm.add_widget(Screen(name='Screen 2'))
        wid = self.sm.get_screen('Screen 1')
        wid.add_widget(Button(text='Screen 1',on_release=self.change))
        return self.sm

    def change(self,*kwargs):
        self.sm.current = 'Screen 2'
        self.s2 = self.sm.get_screen('Screen 2')
        self.s2.add_widget(MyToolBar())
        self.rv = RV(path = 'Directory Path')
        self.s2.add_widget(self.rv)


    def add_checkicon(self,instance):

        if instance.parent.children[0].icon == 'check-bold':
            if instance.source in self.check_dict:
                instance.parent.children[0].icon = ''
                self.check_dict.pop(path)
            print(self.check_dict)
        else:
            if instance.source not in self.check_dict:
                instance.parent.children[0].icon = 'check-bold'
                self.check_dict[path] = instance
            print(self.check_dict)


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

任何帮助都很感谢!

解决方法

这是RecycleView小部件的标准行为-https://kivy.org/doc/stable/api-kivy.uix.recycleview.html#module-kivy.uix.recycleview

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...