【问题标题】:How to refresh kivy RecycleView every time data changes?每次数据更改时如何刷新kivy RecycleView?
【发布时间】:2018-02-09 21:49:37
【问题描述】:

我正在尝试创建一个简单的考勤应用程序。

当程序启动时,所有标签都在取消选择列表中

预期行为:当任何标签被选中时,数据会移动到选中的列表中,现在选中的标签位于连接列表的末尾。然后 RecycleView 刷新以显示此更改。

所以我设法让数据从一个列表移动到另一个列表,但我无法让 RecycleView 刷新

我尝试使用 id 但失败了

我希望有人可以帮助我。我认为这对于知识渊博的人来说是一个常规问题,但对于像我这样的菜鸟来说却不是。

我是第一次在这个网站上提问,所以提前抱歉

代码如下:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.properties import ListProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
from datetime import datetime
import kivy
from kivy.config import Config
Config.set('graphics', 'width', '300')
Config.set('graphics', 'height', '500')

importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
                'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']
deselected_list = importedlist[:]
selected_list = []

class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    ''' Adds selection and focus behaviour to the view. '''

class SelectableLabel(RecycleDataViewBehavior, Label):
    ''' Add selection support to the Label '''
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        ''' Catch and handle the view changes '''
        self.index = index
        return super(SelectableLabel, self).refresh_view_attrs(
            rv, index, data)

    def on_touch_down(self, touch):
        ''' Add selection on touch down '''
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        ''' Respond to the selection of items in the view.
        and add/remove items from lists
        '''
        self.selected = is_selected
        if self.selected and self.text in deselected_list:
            selected_list.append(self.text)
            deselected_list.remove(self.text)
            print(selected_list)
        elif not self.selected and self.text in selected_list:
            deselected_list.append(self.text)
            selected_list.remove(self.text)
            print(deselected_list)

class RV(RecycleView):
    # this needs to be updated every time any label is selected or deselected
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = ([{'text': str(row)} for row in sorted(deselected_list)]
                     + [{'text': str(row)} for row in sorted(selected_list)])

class Screen(BoxLayout):
    now = datetime.now()

    def nowdate(self):
        return self.now.strftime('%d')

    def nowmonth(self):
        return self.now.strftime('%m')

    def nowyear(self):
        return self.now.strftime('%y')

    def nowhour(self):
        return self.now.strftime('%H')

    def nowminute(self):
        return self.now.strftime('%M')

Builder.load_string('''
#:import datetime datetime

<Screen>:
    orientation: 'vertical'
    BoxLayout:
        size_hint_y: None
        height: 30
        TextInput:
            id: date
            text: root.nowdate()
        TextInput:
            id: date
            text: root.nowmonth()
        TextInput:
            id: date
            text: root.nowyear()
        TextInput:
            id: date
            text: root.nowhour()
        TextInput:
            id: date
            text: root.nowminute()
        Button:
    RV:
        viewclass: 'SelectableLabel'
        SelectableRecycleBoxLayout:
            default_size: None, dp(45)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
            multiselect: True
            touch_multiselect: True
    Button:
        size_hint_y: None
        height: 30

<SelectableLabel>:
    # Draw a background to indicate selection
    canvas.before:
        Color:
            rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
        Rectangle:
            pos: self.pos
            size: self.size

''')

class TestApp(App):
    def build(self):
        return Screen()

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

【问题讨论】:

  • '预期的行为:当任何标签被选中时,数据移动到选中的列表,现在选中的标签位于连接列表的末尾。然后 RecycleView 刷新以显示此更改。您的意思是要在将标签添加到新列表时删除它们吗?因此,如果我单击“Carrol Faircloth”,她的名字会从 RecycleViewLayout 中删除吗?所以我们然后在房车上看到除了她以外的其他人?
  • 非常感谢您的回复!我的意思是“Carrol Faircloth”在这个列表的底部。如果我想改变主意并取消选择她,她将留在底部。所有选定的标签都将位于列表的底部,它们实际上将在一个单独的排序列表中(取消选择和选定的列表将在另一个之下,并且每次我选择或取消选择任何内容时它们都会排序)。我仍然尝试想办法做到这一点,但主要问题是我正在使用 RecycleView 并且我不明白如何将每个列表项与这种刷新行为绑定
  • 为了进一步澄清,我们有两个列表,两个回收视图(每个列表一个),然后是这些列表中的一堆标签?在我写一个例子之前先确定一下。
  • 使用 ids 我也尝试将其添加到 SelectableLabel: on_selected: self.ids.RV_id.refresh_from_layout() 并且我仍然收到错误使用 self.parent 没有帮助,因为 SelectableLabel 父级是 RecycleBoxLayout (不是 RecycleView)
  • 是的。我不认为它们是两个独立的 RecycleView,但它们可能是)

标签: python kivy


【解决方案1】:

好的,所以我确实绊倒了几次试图解决这个问题,但我想我明白了。我所做的是创建两个回收视图和一个可以访问 ButtonBehaviors 的 CustomLabel,因此您可以使用“on_press”而不是“on_touch_down”(它通过整个树而不是与元素交互的元素传播)。

Example Video

py文件:

from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.floatlayout import FloatLayout

# Create a Custom ButtonLabel that can use on_press
class ButtonLabel(ButtonBehavior, Label):

    # App.get_running_app() lets us traverse all the way through our app from
    # the very top, which allows us access to any id. In this case we are accessing
    # the content of our selected_list_view of our app
    @property
    def selected_list_content(self):
        return App.get_running_app().root.ids.selected_list.ids.content

    # And in this case, we're accessing the content of our deselected_list_view
    @property
    def deselected_list_content(self):
        return App.get_running_app().root.ids.deselected_list.ids.content

    # This is our callback that our Label's will call when pressed
    def change_location(self):

        # If the label's parent is equal* the selected list, we remove the label from its
        # parent, and then we add it to the other list
        if self.parent == self.selected_list_content:
            self.parent.remove_widget(self)
            self.deselected_list_content.add_widget(self)

        # If the label's parent is not the selected list, then it is the deselected list
        # so we remove it from its parent and add it to the selected list
        else:
            self.parent.remove_widget(self)
            self.selected_list_content.add_widget(self)

    #* Note: Kivy uses weak references. This is why we use ==, and not 'is'

# We create a CustomRecycleView that we will define in our kv file      
class CustomRecycleView(RecycleView):
    pass

class MainWindow(FloatLayout):
    pass

class ExampleApp(App):

    def build(self):
        # We create an instance of the MainWindow class, so we can access its id
        # to import our list. Otherwise we would have nothing to add the list too
        main_window = MainWindow()
        importedlist = ['Novella Varela', 'Caroll Faircloth', 'Douglas Schissler',
                'Rolande Hassell', 'Hayley Rivero', 'Niesha Dungy', 'Winfred Dejonge', 'Venetta Milum']

        # We create a Label for each Name in our imported list, and then add it
        # to the content of selected list as a default
        # I'm sure you'll be importing our own lists in a different manner
        # This is just for the example
        for name in importedlist:
            NameLabel = ButtonLabel(text=(name))
            main_window.ids.selected_list.ids.content.add_widget(NameLabel)
        return main_window

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

kv 文件:

#:kivy 1.10.0

# We create a reference to the ButtonLabel class in our py file
<ButtonLabel>:
    # We add our callback to our ButtonLabels on press event, on_press
    on_press: root.change_location()

# We create a reference to our CustomRecycleView class in our py file
<CustomRecycleView>:
    # We create a GridLayout to store all of the content in our RecycleView
    GridLayout:
        # We give it the id content so we can define the two property values in
        # ButtonLabel class in the py file
        id: content
        size_hint_y: None

        # One column because we want it to be vertical list list
        cols: 1

        # This set up, as well as size_hint_y set to None
        # is so we can scroll vertically without issue
        row_default_height: 60
        height: self.minimum_height

<MainWindow>:
    # We then create two instances of our CustomRecycleView, give them the ids
    # referenced by the ButtonLabel methods as well as give them equal share of the
    # screen space so they do not step on each others toes
    # The canvas here is just for prototyping purposes to make sure they are the
    # properly defined sizes. You can do whatever with them you would like tbh.
    CustomRecycleView:
        id: selected_list
        size_hint: 1, .5
        pos_hint: {'x': 0, 'y': .5}
        canvas:
            Color:
                rgba: 100, 0, 0, .2
            Rectangle:
                size: self.size
                pos: self.pos
    CustomRecycleView:
        id: deselected_list
        size_hint: 1, .45
        canvas:
            Color:
                rgba: 0, 0, 100, .2
            Rectangle:
                size: self.size
                pos: self.pos

【讨论】:

  • 你真是太酷了。我一定会尝试了解您所做的到底是什么。我将使用您的实现,直到我可能能够按照我一开始的意图使这一切都在一个 RecycleView 内工作。再次感谢!非常感谢您的帮助)
  • 为什么需要在 same RecycleView 中包含两个不同的列表?。从这里看来是不必要的。您可能会做一些事情,例如将两个回收视图放入垂直 boxlayout 中,这样当您移动 boxlayout 时,两个视图都会随之移动。
  • 我想要一份清单,因为它实际上是一份清单——一份有学生的清单。碰巧他们中的一些人在场。它们移到底部,因为我在选择它们后不需要查看它们。我需要查看它们的唯一时间是我选择错误或者我想检查我是否选择正确。在您的解决方案中,我们可以一直看到它们,而且我们有两个可滚动的屏幕,而只有一个就足够了。总之非常感谢。我仍然学到了很多东西
猜你喜欢
  • 2021-10-15
  • 1970-01-01
  • 1970-01-01
  • 2020-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多