#python #kivy
#python #kivy
Вопрос:
Я использую Kivy recycleview
для отображения списка данных в виде таблицы. Я использовал пример из документации в качестве основы для своей реализации.
В моей программе RecycleDataView основан на BoxLayout, а его дочерние виджеты генерируются динамически.
Кажется, это работает, но порядок, в котором отображаются элементы, иногда меняется на противоположный и продолжает меняться при изменении размера окна. Что еще хуже, если вы прокрутите вниз, макет станет совершенно сумасшедшим. Этого не произойдет, если я буду использовать простую метку в качестве класса элемента, поэтому я предполагаю, что проблема в моей динамически созданной логике виджета, но я не понимаю почему.
Вот некоторый минимальный код, который показывает проблему.
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<RV>:
viewclass: 'RVItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class Attribute:
def __init__(self, name, values):
self.name = name
self.values = values
class RVItem(RecycleDataViewBehavior, BoxLayout):
index = None
attribute = ObjectProperty()
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.create_widgets(data.pop('attribute', None))
return super(RVItem, self).refresh_view_attrs(
rv, index, data)
def create_widgets(self, value: Attribute):
"""Dynamically create the needed Widgets"""
if value is None:
return
self.add_widget(Label(text=value.name, height=self.height, size_hint=(1, None)))
if not isinstance(value.values, dict):
self.add_widget(Label(text=value.values, height=self.height, size_hint=(1, None)))
else:
for _, v in value.values.items():
self.add_widget(Label(text=v, height=self.height, size_hint=(1, None)))
image_button = Button(text=' ')
#image_button.source = 'wm_ui/glyphs/plus.png'
image_button.size_hint = None, None
image_button.size = "30sp", "30sp"
image_button.bind(on_press=self.add_button_pressed)
self.add_widget(image_button)
def add_button_pressed(self, s):
print("Would add a new item to the recycleview if implemented.")
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = [{'attribute': Attribute(str(x), "test")} for x in range(100)]
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
Для простоты я изменил некоторые виджеты на их базовые классы (например, ImageButton и Label)
При запуске приложения вы должны увидеть, что порядок элементов изменен на обратный и по какой-то причине начинается с 10 вместо 100.
После изменения размера окна с помощью мыши в одном из углов окна вы должны увидеть, что порядок содержимого постоянно меняется.
А если вы прокрутите вниз, все станет еще более безумным.
К сожалению, я понятия не имею, что вызывает такое поведение. Я уже разрабатывал некоторые приложения Kivy раньше, но это мое первое по-настоящему глубокое погружение, в котором используется больше, чем просто метки и несколько входных данных.
Комментарии:
1. Существует ли фиксированное максимальное количество
Label
виджетов, которые будут отображаться в одномRVItem
?2. @JohnAnderson Я ожидаю, что их будет не более 10, но вы никогда не знаете, что могут сделать пользователи. Количество меток равно как минимум двум, но определенного максимума нет, потому что оно зависит от количества значений, установленных пользователем (определенных в классе атрибутов). Думайте об этом как о столбцах пользовательской таблицы. Я не могу знать, сколько виджетов будет заранее.
3.
RecyleView
Выполняет то, что от него ожидается, то есть повторно использует вашиRVItem
виджеты. Проблема в том, что выrefresh_view_attrs()
добавляете большеLabel
иButton
виджетов к тем, которые были переработаныRVItems
. Вот почему при прокрутке вниз вы видите все больше и большеLabel
иButton
виджетов в каждомRVItem
. Поскольку ониRVItems
перерабатываются, вамrefresh_view_attrs()
необходимо установить ВСЕ атрибуты дляRVItem
(включаяname
). Кроме того, поскольку выrefresh_view_attrs()
создаете все эти виджеты, вы не используетеRecycle
частьRecycleView
преимуществ.
Ответ №1:
Вот модификация вашего кода, которая, похоже, работает правильно:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
Builder.load_string('''
<RV>:
viewclass: 'RVItem'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class Attribute:
def __init__(self, name, values):
self.name = name
self.values = values
class RVItem(RecycleDataViewBehavior, BoxLayout):
index = None
attribute = ObjectProperty()
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.create_widgets(data['attribute'])
return super(RVItem, self).refresh_view_attrs(
rv, index, data)
def create_widgets(self, value: Attribute):
rv = App.get_running_app().root
rv.cache_widgets(self.children)
self.clear_widgets()
label = rv.get_label()
label.text = value.name
self.add_widget(label)
if isinstance(value.values, dict):
for _,v in value.values.items():
label = rv.get_label()
label.text = v
self.add_widget(label)
else:
label = rv.get_label()
label.text = value.values
self.add_widget(label)
image_button = rv.get_button()
image_button.text = ' '
image_button.size_hint = None, None
image_button.size = "30sp", "30sp"
image_button.bind(on_press=self.add_button_pressed)
self.add_widget(image_button)
def add_button_pressed(self, s):
print("Would add a new item to the recycleview if implemented.")
class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.label_cache = []
self.button_cache = []
self.data = [{'attribute': Attribute(str(x), "test")} for x in range(100)]
for i in range(100):
if i % 5 == 0:
self.data[i]['attribute'].values = {'1': 'test1', '2': 'test2', '3': 'test3'}
def get_button(self):
if len(self.button_cache) > 0:
return self.button_cache.pop()
else:
return Button()
def get_label(self):
if len(self.label_cache) > 0:
return self.label_cache.pop()
else:
return Label()
def cache_widgets(self, widgets):
for w in widgets:
if isinstance(w, Button):
self.button_cache.append(w)
else:
self.label_cache.append(w)
class TestApp(App):
def build(self):
return RV()
if __name__ == '__main__':
TestApp().run()
В этой версии refresh_view_attrs
метод всегда устанавливает все атрибуты RVItem
. Строка
self.create_widgets(data.pop('attribute', None))
заменяется на
self.create_widgets(data['attribute'])
потому что pop()
фактически удаляются данные, чего, я думаю, вы не хотите делать.
У RV
класса теперь есть кэш для Label
виджетов и другой для Button
виджетов, и они перерабатываются (аналогично тому, что RecycleView
делает. create_widgets
Метод удаляет все дочерние элементы RVItem
и добавляет их в кэш, затем перерабатывает или создает виджеты, по мере необходимости, для заполнения RVItem
.
Я добавил дополнительные элементы в values
dict для некоторых данных, чтобы проиллюстрировать, как это работает.
Комментарии:
1. Спасибо, я думаю, у меня было неправильное представление о том, как работает часть переработки. Не могли бы вы порекомендовать мне использовать фабрику и динамически создавать viewclasses с фиксированным количеством виджетов вместо этого?
2. Если созданное
RVItem
всегда имеет одинаковое количество дочерних элементов, то все ониRVItem
могут быть переработаныRecycleView
, иrefresh_view_attrs()
метод может просто установить атрибутыRVItem
. Итак, если для вас возможно фиксированное количество дочерних элементов, это упростит задачу.