RecycleView

在 1.10.0 版本加入.

RecycleView 提供了一种灵活的模型,用于查看大型数据集的选定部分。其目的在于避免因生成大量控件以显示众多数据项而可能导致的性能下降。

警告

由于 RecycleView 会复用控件,因此对单个控件的任何状态更改都会在复用时保留在该控件上,即使 RecycleView 分配给它的 data 发生了变化,除非在 data 中跟踪了完整状态(见下文)。

视图通过处理 viewclass 的实例。其设计基于 MVC(模型-视图-控制器)模式。

  • 模型:模型由您通过字典列表传入的 data 构成。

  • 视图:视图在布局和视图之间进行拆分,并通过适配器实现。

  • 控制器:控制器决定逻辑交互,由 RecycleViewBehavior 实现。

这些是抽象类,不能直接使用。默认的具体实现包括:模型使用 RecycleDataModel,视图使用 RecycleLayout,控制器使用 RecycleView

当实例化RecycleView时,它会自动创建视图和数据类。然而,必须手动创建布局类并将其添加到RecycleView中。

当作为RecycleView的子组件添加时,会自动创建一个布局管理器作为:attr:~RecycleViewBehavior.layout_manager。同样,在移除时也会如此。一个要求是,布局管理器必须作为子组件包含在RecycleView的控件树中的某个位置,以便能够找到视口。

一个最小示例可能如下所示:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView


Builder.load_string('''
<RV>:
    viewclass: 'Label'
    RecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
''')

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = [{'text': str(x)} for x in range(100)]


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

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

为了在视图中支持选择功能,您可以按如下方式添加所需的行为:

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.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior

Builder.load_string('''
<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
<RV>:
    viewclass: 'SelectableLabel'
    SelectableRecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
        multiselect: True
        touch_multiselect: True
''')


class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
                                 RecycleBoxLayout):
    ''' Adds selection and focus behavior 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. '''
        self.selected = is_selected
        if is_selected:
            print("selection changed to {0}".format(rv.data[index]))
        else:
            print("selection removed for {0}".format(rv.data[index]))


class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = [{'text': str(x)} for x in range(100)]


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

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

请参阅 examples/widgets/recycleview/basic_data.py 文件,以获取更完整的示例。

Viewclass 状态

由于 RecycleView 会根据需要重用或实例化 viewclass 控件,因此控件的顺序和内容是可变的。所以,对单个控件的任何状态更改都会保留在该控件上,即使从 data 字典分配给它的数据发生变化,除非 data 跟踪这些更改,或者在重用时手动刷新它们。

在viewclass控件中管理状态变化有两种方法:

  1. 将状态存储在RecycleView.data模型中。

  2. 通过捕获 data 的更新并手动刷新,即时生成状态变化。

示例:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import BooleanProperty, StringProperty

Builder.load_string('''
<StatefulLabel>:
    active: stored_state.active
    CheckBox:
        id: stored_state
        active: root.active
        on_release: root.store_checkbox_state()
    Label:
        text: root.text
    Label:
        id: generate_state
        text: root.generated_state_text

<RV>:
    viewclass: 'StatefulLabel'
    RecycleBoxLayout:
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
''')

class StatefulLabel(RecycleDataViewBehavior, BoxLayout):
    text = StringProperty()
    generated_state_text = StringProperty()
    active = BooleanProperty()
    index = 0

    '''
    To change a viewclass' state as the data assigned to it changes,
    overload the refresh_view_attrs function (inherited from
    RecycleDataViewBehavior)
    '''
    def refresh_view_attrs(self, rv, index, data):
        self.index = index
        if data['text'] == '0':
            self.generated_state_text = "is zero"
        elif int(data['text']) % 2 == 1:
            self.generated_state_text = "is odd"
        else:
            self.generated_state_text = "is even"
        super(StatefulLabel, self).refresh_view_attrs(rv, index, data)

    '''
    To keep state changes in the viewclass with associated data,
    they can be explicitly stored in the RecycleView's data object
    '''
    def store_checkbox_state(self):
        rv = App.get_running_app().rv
        rv.data[self.index]['active'] = self.active

class RV(RecycleView, App):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = [{'text': str(x), 'active': False} for x in range(10)]
        App.get_running_app().rv = self

    def build(self):
        return self

if __name__ == '__main__':
    RV().run()
待办事项:
  • 清除缓存类实例的方法。

  • 测试当视图无法找到时的情况(例如,viewclass 为 None)。

  • 修复选择跳转。

警告

当视图被复用时,如果数据保持不变,它们可能不会触发更新。

class kivy.uix.recycleview.RecycleView(**kwargs)[源代码]

基类:RecycleViewBehavior, ScrollView

RecycleView 是一个灵活的视图,用于在大数据集中提供一个有限的窗口。

请参阅模块文档以获取更多信息。

add_widget(widget, *args, **kwargs)[源代码]

将此小部件添加为当前小部件的子级。

参数:
widget: Widget

要添加到我们子控件列表中的控件。

index:整数,默认值为0

在列表中插入小部件的索引。请注意,默认值为0意味着小部件被插入到列表的开头,因此会绘制在其他同级小部件之上。关于索引和小部件层次结构的完整讨论,请参阅 小部件编程指南

在 1.0.5 版本加入.

canvas:字符串,默认为 None

用于添加控件画布的Canvas。可以是'before'、'after'或None(默认画布)。

在 1.9.0 版本加入.

>>> from kivy.uix.button import Button
>>> from kivy.uix.slider import Slider
>>> root = Widget()
>>> root.add_widget(Button())
>>> slider = Slider()
>>> root.add_widget(slider)
data

当前视图适配器使用的数据。这是一个字典列表,其键映射到 viewclass 的相应属性名称。

data 是一个 AliasProperty,用于获取和设置生成视图所用的数据。

key_viewclass

key_viewclass 是一个 AliasProperty,用于获取和设置当前 layout_manager 的键视图类。

remove_widget(widget, *args, **kwargs)[源代码]

从该部件的子部件中移除一个部件。

参数:
widget: Widget

要从我们的子控件列表中移除的控件。

>>> from kivy.uix.button import Button
>>> root = Widget()
>>> button = Button()
>>> root.add_widget(button)
>>> root.remove_widget(button)
viewclass

当前 layout_manager 所使用的视图类。

viewclass 是一个 AliasProperty,用于获取和设置在视图中呈现的各个项目所生成的类。

class kivy.uix.recycleview.RecycleViewBehavior(**kwargs)[源代码]

基类:object

RecycleViewBehavior 提供了一种行为模型,基于此模型构建了 RecycleView。两者共同提供了一种可扩展且灵活的方式,用于在大型数据集上生成具有有限窗口的视图。

请参阅模块文档以获取更多信息。

data_model

负责维护数据集的数据模型。

data_model 是一个 AliasProperty,用于获取和设置当前的数据模型。

layout_manager

负责在 RecycleView 中定位视图的布局管理器。

layout_manager 是一个 AliasProperty,用于获取和设置 layout_manager。

refresh_from_data(*largs, **kwargs)[源代码]

当数据发生变化时,应调用此方法。数据变化通常意味着由于源数据已改变,所有内容都应重新计算。

该方法自动绑定到 RecycleDataModelBehavior 类的 on_data_changed 方法上,因此会响应并接受该事件的关键字参数。

可以手动调用以触发更新。

refresh_from_layout(*largs, **kwargs)[源代码]

当布局发生变化或需要变化时,应调用此方法。通常在布局参数发生改变,因此需要重新计算布局时调用。

refresh_from_viewport(*largs)[源代码]

当视口发生变化且必须更新显示的数据时,应调用此方法。数据和布局都不会被重新计算。

view_adapter

负责提供视图中代表数据集中项目的适配器。

view_adapter 是一个 AliasProperty,用于获取和设置当前的视图适配器。