python - kivy 中的大量小部件 - 优化
问题描述
我有一个用kivymd编写的音乐应用程序,它会扫描您的默认下载目录并提取有关您的音频文件的信息。(基本上是检索IDV3标签和其他东西)
我的桌面上最多只有 20 个音频文件,这应该不是问题,但我决定用许多音频(如 120 个)文件测试我的程序,结果很糟糕。
界面太慢了,几乎无法正常工作。我应该如何在 kivy 中显示大量小部件而不会导致这种灾难性的性能下降?
另外,我怎样才能使小部件的加载异步,以便它们在启动时也不会占用大量时间?
我的代码的最小可重现示例:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
@property
def path(self):
return self._path
@property
def title(self):
return self._title
@property
def artist(self):
return self._artist
@property
def album_cover(self):
return self._album_cover
class SongCard(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
song_obj = ObjectProperty(Song("dummy song"), rebind=True)
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
radius: ("10dp",)
canvas.before:
Color:
rgba: app.theme_cls.bg_dark
RoundedRectangle:
pos: self.pos
size: self.size
radius: root.radius
FitImage:
source: root.song_obj.album_cover
MDLabel:
text: root.song_obj.title
adaptive_height: True
MDLabel:
text: root.song_obj.artist
adaptive_height: True
ScrollView:
do_scroll_x: False
MDGridLayout:
id: song_grid
cols: 2
adaptive_height: True
spacing: "10dp"
padding: "10dp"
''')
def build(self):
return self.kv
def on_start(self):
for _ in range(25):
for audio_file in os.listdir(os.path.join(os.path.expanduser('~'), "Downloads")):
if audio_file.endswith(".mp3"):
song_obj = Song(audio_file)
self.kv.ids.song_grid.add_widget(
SongCard(
song_obj=song_obj
)
)
if __name__ == '__main__':
MainApp().run()
解决方案
您可以使用 RecycleView 类来管理小部件的数量并帮助您的程序能够接受大量的小部件,ScrollView 很棒但是 recycleview 将数据作为字典管理,然后您可以使用 BoxLayout 小部件下的两个 BoxLayout 类具有 GridLayout 类功能,我用 213 个元素对此进行了测试,它适用于许多小部件和良好的性能:
import os
from kivy.lang import Builder
from kivy.uix.behaviors import ButtonBehavior
from kivy.properties import ObjectProperty, StringProperty
from kivymd.uix.behaviors import RectangularRippleBehavior
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.app import MDApp
from kivy.uix.recyclegridlayout import RecycleGridLayout
class Song:
"""Class for extracting information about an audio file."""
def __init__(self, path: str):
self._path = path
self._title = "My Song"
self._artist = "Unknown artist"
# the album cover is going to a texture retrieved from the file itself
# but in this case, I will refer to a file.
self._album_cover = "Default-Album-Cover.jpg"
## @property
## def path(self):
## return self._path
##
## @property
## def title(self):
## return self._title
##
## @property
## def artist(self):
## return self._artist
##
## @property
## def album_cover(self):
## return self._album_cover
class SongCard(MDBoxLayout):
"""Custom widget for creating cards."""
#song_obj = ObjectProperty(Song("dummy song"), rebind=True)
song_obj = StringProperty()
class B2(ButtonBehavior, RectangularRippleBehavior, MDBoxLayout):
"""Custom widget for creating cards."""
pass
class MainApp(MDApp):
def __init__(self, **kwargs):
super(MainApp, self).__init__(**kwargs)
self.theme_cls.theme_style = "Dark"
self.kv = Builder.load_string('''
#:kivy 2.0.0
<SongCard>:
orientation: "vertical"
size_hint_y: None
height: "300dp"
#radius: ("10dp",)
BoxLayout:
padding: dp(10)
spacing: dp(5)
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [0,1,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
size_hint: 1,1
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
B2:
padding: dp(10)
canvas.before:
Color:
rgba: [1,0,0,.2]
RoundedRectangle:
pos: self.pos
size: self.size
radius: [30,]
orientation: "vertical"
spacing: dp(5)
FitImage:
source: "Help5/cc.png" #root.song_obj.album_cover
MDLabel:
text: root.song_obj[:40] #.title
adaptive_height: True
MDLabel:
text: " - jbsidis" #.artist
adaptive_height: True
Screen:
BoxLayout:
orientation: "vertical"
#do_scroll_x: False
RecycleSupportGridLayoutjbsidis:
id: song_grid
key_viewclass: 'viewclass'
key_size: 'height'
RecycleBoxLayout:
padding: dp(10)
default_size: None, dp(248)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
<RecycleSupportGridLayoutjbsidis@RecycleView>:
''')
def build(self):
return self.kv
def on_start(self):
#for _ in range(25):
for audio_file in os.listdir("/media/jbsidis/Android/Q/D/Anthony Robbins/"): #os.path.join(os.path.expanduser('~'), "Downloads")
#if audio_file.endswith(".mp3"):
#song_obj = Song(audio_file)
self.kv.ids.song_grid.data.append(
{
"viewclass": "SongCard",
"song_obj": audio_file
}
)
## {
## "viewclass": "CustomOneLineIconListItem",
## "icon": name_icon,
## "text": name_icon,
## "on_release": lambda:Clipboard.copy(name_icon),
## }
## self.kv.ids.song_grid.add_widget(
## SongCard(
## song_obj=song_obj
## )
## )
if __name__ == '__main__':
MainApp().run()
推荐阅读
- javascript - 将 csv 文件添加到 webpack 构建
- javascript - 从 AmCharts 获取范围值
- python - 检查输入时出错:预期的 dense_input 的形状为 (21,) 但得到的数组的形状为 (1,)
- ios - 在 Swift 4.2 中观察不会被触发
- sql - 带有 Where 子句的 SQL Left Join 查询从第一个表中删除一些行
- c - struct内存分配,内存分配应该是4的倍数
- python - 气流网络服务器未从 1.10 开始
- delphi - 使用 TTHread 同时打开更多 ClientDataSet
- button - 如何控制对由 SharePoint 安全组控制的 InfoPath 表单按钮的访问
- c# - 从存储过程中读取标量值数据并在 MVC 视图中显示