Kivy:如何动态初始化 RecycleView 的视图类?
Kivy: How to initialize the viewclass of the RecycleView dynamically?
最近一直在纠结在kivy中使用RecycleView。我做了很多测试,有点困惑。
我的目标
我想创建一个回收视图,其中每个 Row
包含几个 Label
(即列),但列数是动态设置的(当然所有行都相同)。通常,我有一个数据库,我想显示 table 但我想动态更改我显示的列。
以前也有类似的问题,不过都是处理固定数量的值来设置。
有效方法
- 如果列数是固定的,可以使用属性link编辑值
- 我可以使用
add_widget
动态添加小部件
什么不起作用
- 如果单元格是动态添加的,我不能link将kv小部件中的值内容python变量
- 如果 Label 小部件的数量是固定的,我可以动态更新东西(如示例)但不能直接初始化。
这是一个例子,其中包含我尝试过的所有不同可能性:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
kv = """
<Row>:
id: row
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value1: ''
value2: ''
value3: ''
value4: ''
value5: ''
Label: #Label 1
text: root.value1
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Update list'
on_press: root.update()
RecycleView:
id: rvlist
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Row(BoxLayout):
constant_value = 'constant content from class data'
value1 = StringProperty('default 1')
value2 = StringProperty('default 2')
value3 = StringProperty('default 3')
value4 = StringProperty('default 4')
value5 = StringProperty('default 5')
# value5,....
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.add_widget(Label(text = 'constant content')) # Label 2
self.add_widget(Label(text = self.constant_value)) #Label 3
self.add_widget(Label(text = self.value4)) # Label 4
self.add_widget(Label(text = self.value5)) # Label 5
for x in kwargs:
self.add_widget(Label(text = 'content from **kwargs'))# Label 6
class Test(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.ids.rvlist.data = [{'value1': 'init content from parent class',
'value4': 'init content from parent class'} for x in range(15)]
def update(self):
self.ids.rvlist.data = [{'value1': 'updated content from parent class dynamic widget',
'value4': 'updated content from parent class dynamic widget',
'value5': 'updated content from parent class static widget'} for x in range(15)]
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
行为如下:
- Label1:这与文档中的定义相同。这很好用,但我需要在行 class 中定义与列的最大可能值一样多的值,并且只使用其中的几个。看起来有点黑...
- Label2:按预期工作。这是一个不变的内容。但这不是我需要的(我想用动态生成的内容填充这些行)
- Label3:Label2 的变体,不是我需要的。但是有效。
- Label4:这里的label是在初始化的时候设置的。 初始化和更新不起作用,可能是因为小部件尚未创建,所以
self.value4
尚不存在。有解决方法吗?
- Label5:更新时设置标签。 初始化和更新都不起作用。类似于4.
- Label6: 甚至没有创建。我原以为
data
将是传递给 class 的参数之一以进行初始化,这样我就可以为每个数据创建一个标签。我想那将是最理想的情况。
它提出了问题:
- 如何动态创建一个 kv 属性 和 link 到另一个 属性? (类似于 self.add_widget(Label(value5 = parent.value5,text = self.value5))
。这将允许在初始化后更新文本(不知何故就像我的 label1)
- 如何在初始化期间访问 data
?
- 如何使用 self.value
初始化标签?
我希望它足够清楚...我自己也很困惑。
经过一番摸索,我终于明白了如何实现我想要的,这不是那么简单。
我最初的 post 有两处错误:
- 如前所述,当我分配 属性 时,小部件尚未初始化。诀窍是使用 Clock.schedule_once
将初始化延迟到下一帧。我在另一个问题中看到了这个技巧(抱歉,我现在找不到它)。
- 由此产生的一个问题是小部件已创建但不能 updated/accessed。为此,我创建了一个回调方法,每次更新视图类中的 data
时都会更新标签。
代码如下:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
from kivy.clock import Clock
kv = """
<Row>:
id: row
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Update list'
on_press: root.update()
RecycleView:
id: rvlist
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Row(BoxLayout):
row_content = ObjectProperty()
def __init__(self,**kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.finish_init,0)
# it delays the end of the initialization to the next frame, once the widget are already created
# and the properties properly initialized
def finish_init(self, dt):
for elt in self.row_content:
self.add_widget(Label(text = elt))
# now, this works properly as the widget is already defined
self.bind(row_content = self.update_row)
def update_row(self, *args):
# right now the update is rough, I delete all the widget and re-add them. Something more subtle
# like only replacing the label which have changed
print(args)
# because of the binding, the value which have changed are passed as a positional argument.
# I use it to set the new value of the labels.
self.clear_widgets()
for elt in args[1]:
self.add_widget(Label(text = elt))
class Test(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.ids.rvlist.data = [{'row_content': [str(i) for i in range(5)]} for x in range(15)]
def update(self):
self.ids.rvlist.data[0]['row_content'] = [str(10*i) for i in range(5)]
self.ids.rvlist.refresh_from_data()
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
使用此代码,我可以创建一个包含多列的 table,并使用可用单元格的数量初始化每一行。如果列数改变,它会自动改变单元格数(可能需要修改一下更新功能)。
一个限制:widget是动态创建的,因此不能直接通过id调用它们。如果您需要在每一行中使用更高级的小部件,您可能需要创建一些功能来管理自己已经创建的小部件(通过存储小部件对应的 self.children
的位置)。
如果您发现更好的做事方式,请告诉我。我仍然发现 Clock.schedule_once(self.finish_init,0)
看起来像一个 hack,我觉得很奇怪,在 kivy 框架中没有更直接的实现。
不确定我是否完全理解您的目标,尤其是您所说的
"dynamically change the columns", "the number of columns is set dynamically"
如果只有一小组预定义的列数选项,而您只想动态地从这些选项中选择一个,那么很简单:定义一个class 为每个选项,然后在初始化 RecycleView
.
之后设置 RecycleView
的 viewclass
属性的值
但是,如果列数有无限(太多)的可能性并且您想要那种 动态 的灵活性,那么 RecycleView
不是最好的这样做的方法,因为 viewclass
是预定义的 class 而不是可变的东西。
最近一直在纠结在kivy中使用RecycleView。我做了很多测试,有点困惑。
我的目标
我想创建一个回收视图,其中每个 Row
包含几个 Label
(即列),但列数是动态设置的(当然所有行都相同)。通常,我有一个数据库,我想显示 table 但我想动态更改我显示的列。
以前也有类似的问题,不过都是处理固定数量的值来设置。
有效方法
- 如果列数是固定的,可以使用属性link编辑值
- 我可以使用
add_widget
动态添加小部件
什么不起作用
- 如果单元格是动态添加的,我不能link将kv小部件中的值内容python变量
- 如果 Label 小部件的数量是固定的,我可以动态更新东西(如示例)但不能直接初始化。
这是一个例子,其中包含我尝试过的所有不同可能性:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
kv = """
<Row>:
id: row
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value1: ''
value2: ''
value3: ''
value4: ''
value5: ''
Label: #Label 1
text: root.value1
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Update list'
on_press: root.update()
RecycleView:
id: rvlist
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Row(BoxLayout):
constant_value = 'constant content from class data'
value1 = StringProperty('default 1')
value2 = StringProperty('default 2')
value3 = StringProperty('default 3')
value4 = StringProperty('default 4')
value5 = StringProperty('default 5')
# value5,....
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.add_widget(Label(text = 'constant content')) # Label 2
self.add_widget(Label(text = self.constant_value)) #Label 3
self.add_widget(Label(text = self.value4)) # Label 4
self.add_widget(Label(text = self.value5)) # Label 5
for x in kwargs:
self.add_widget(Label(text = 'content from **kwargs'))# Label 6
class Test(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.ids.rvlist.data = [{'value1': 'init content from parent class',
'value4': 'init content from parent class'} for x in range(15)]
def update(self):
self.ids.rvlist.data = [{'value1': 'updated content from parent class dynamic widget',
'value4': 'updated content from parent class dynamic widget',
'value5': 'updated content from parent class static widget'} for x in range(15)]
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
行为如下:
- Label1:这与文档中的定义相同。这很好用,但我需要在行 class 中定义与列的最大可能值一样多的值,并且只使用其中的几个。看起来有点黑...
- Label2:按预期工作。这是一个不变的内容。但这不是我需要的(我想用动态生成的内容填充这些行)
- Label3:Label2 的变体,不是我需要的。但是有效。
- Label4:这里的label是在初始化的时候设置的。 初始化和更新不起作用,可能是因为小部件尚未创建,所以
self.value4
尚不存在。有解决方法吗? - Label5:更新时设置标签。 初始化和更新都不起作用。类似于4.
- Label6: 甚至没有创建。我原以为
data
将是传递给 class 的参数之一以进行初始化,这样我就可以为每个数据创建一个标签。我想那将是最理想的情况。
它提出了问题:
- 如何动态创建一个 kv 属性 和 link 到另一个 属性? (类似于 self.add_widget(Label(value5 = parent.value5,text = self.value5))
。这将允许在初始化后更新文本(不知何故就像我的 label1)
- 如何在初始化期间访问 data
?
- 如何使用 self.value
初始化标签?
我希望它足够清楚...我自己也很困惑。
经过一番摸索,我终于明白了如何实现我想要的,这不是那么简单。
我最初的 post 有两处错误:
- 如前所述,当我分配 属性 时,小部件尚未初始化。诀窍是使用 Clock.schedule_once
将初始化延迟到下一帧。我在另一个问题中看到了这个技巧(抱歉,我现在找不到它)。
- 由此产生的一个问题是小部件已创建但不能 updated/accessed。为此,我创建了一个回调方法,每次更新视图类中的 data
时都会更新标签。
代码如下:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView
from kivy.clock import Clock
kv = """
<Row>:
id: row
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
orientation: 'vertical'
GridLayout:
cols: 3
rows: 2
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Update list'
on_press: root.update()
RecycleView:
id: rvlist
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
Builder.load_string(kv)
class Row(BoxLayout):
row_content = ObjectProperty()
def __init__(self,**kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.finish_init,0)
# it delays the end of the initialization to the next frame, once the widget are already created
# and the properties properly initialized
def finish_init(self, dt):
for elt in self.row_content:
self.add_widget(Label(text = elt))
# now, this works properly as the widget is already defined
self.bind(row_content = self.update_row)
def update_row(self, *args):
# right now the update is rough, I delete all the widget and re-add them. Something more subtle
# like only replacing the label which have changed
print(args)
# because of the binding, the value which have changed are passed as a positional argument.
# I use it to set the new value of the labels.
self.clear_widgets()
for elt in args[1]:
self.add_widget(Label(text = elt))
class Test(BoxLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.ids.rvlist.data = [{'row_content': [str(i) for i in range(5)]} for x in range(15)]
def update(self):
self.ids.rvlist.data[0]['row_content'] = [str(10*i) for i in range(5)]
self.ids.rvlist.refresh_from_data()
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
使用此代码,我可以创建一个包含多列的 table,并使用可用单元格的数量初始化每一行。如果列数改变,它会自动改变单元格数(可能需要修改一下更新功能)。
一个限制:widget是动态创建的,因此不能直接通过id调用它们。如果您需要在每一行中使用更高级的小部件,您可能需要创建一些功能来管理自己已经创建的小部件(通过存储小部件对应的 self.children
的位置)。
如果您发现更好的做事方式,请告诉我。我仍然发现 Clock.schedule_once(self.finish_init,0)
看起来像一个 hack,我觉得很奇怪,在 kivy 框架中没有更直接的实现。
不确定我是否完全理解您的目标,尤其是您所说的
"dynamically change the columns", "the number of columns is set dynamically"
如果只有一小组预定义的列数选项,而您只想动态地从这些选项中选择一个,那么很简单:定义一个class 为每个选项,然后在初始化 RecycleView
.
RecycleView
的 viewclass
属性的值
但是,如果列数有无限(太多)的可能性并且您想要那种 动态 的灵活性,那么 RecycleView
不是最好的这样做的方法,因为 viewclass
是预定义的 class 而不是可变的东西。