Version

Quick search

RecycleView

New in version 1.10.0.

The RecycleView provides a flexible model for viewing selected sections of large data sets. It aims to prevent the performance degradation that can occur when generating large numbers of widgets in order to display many data items.

Warning

Because RecycleView reuses widgets, any state change to a single widget will stay with that widget as it’s reused, even if the data assigned to it by the RecycleView changes, unless the complete state is tracked in data (see below).

The view is generated by processing the data, essentially a list of dicts, and uses these dicts to generate instances of the viewclass as required. Its design is based on the MVC (Model-View-Controller) pattern.

  • Model: The model is formed by data you pass in via a list of dicts.

  • View: The View is split across layout and views and implemented using adapters.

  • Controller: The controller determines the logical interaction and is implemented by RecycleViewBehavior.

These are abstract classes and cannot be used directly. The default concrete implementations are the RecycleDataModel for the model, the RecycleLayout for the view, and the RecycleView for the controller.

When a RecycleView is instantiated, it automatically creates the views and data classes. However, one must manually create the layout classes and add them to the RecycleView.

A layout manager is automatically created as a layout_manager when added as the child of the RecycleView. Similarly when removed. A requirement is that the layout manager must be contained as a child somewhere within the RecycleView’s widget tree so the view port can be found.

A minimal example might look something like this:

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()

In order to support selection in the view, you can add the required behaviors as follows:

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()

Please see the examples/widgets/recycleview/basic_data.py file for a more complete example.

Viewclass State

Because the viewclass widgets are reused or instantiated as needed by the RecycleView, the order and content of the widgets are mutable. So any state change to a single widget will stay with that widget, even when the data assigned to it from the data dict changes, unless data tracks those changes or they are manually refreshed when re-used.

There are two methods for managing state changes in viewclass widgets:

  1. Store state in the RecycleView.data Model

  2. Generate state changes on-the-fly by catching data updates and manually refreshing.

An example:

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()
TODO:
  • Method to clear cached class instances.

  • Test when views cannot be found (e.g. viewclass is None).

  • Fix selection goto.

Warning

When views are re-used they may not trigger if the data remains the same.

class kivy.uix.recycleview.RecycleView(**kwargs)[source]

Bases: kivy.uix.recycleview.RecycleViewBehavior, kivy.uix.scrollview.ScrollView

RecycleView is a flexible view for providing a limited window into a large data set.

See the module documentation for more information.

add_widget(widget, *args, **kwargs)[source]

Add a new widget as a child of this widget.

Parameters:
widget: Widget

Widget to add to our list of children.

index: int, defaults to 0

Index to insert the widget in the list. Notice that the default of 0 means the widget is inserted at the beginning of the list and will thus be drawn on top of other sibling widgets. For a full discussion of the index and widget hierarchy, please see the Widgets Programming Guide.

New in version 1.0.5.

canvas: str, defaults to None

Canvas to add widget’s canvas to. Can be ‘before’, ‘after’ or None for the default canvas.

New in version 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

The data used by the current view adapter. This is a list of dicts whose keys map to the corresponding property names of the viewclass.

data is an AliasProperty that gets and sets the data used to generate the views.

key_viewclass

key_viewclass is an AliasProperty that gets and sets the key viewclass for the current layout_manager.

remove_widget(widget, *args, **kwargs)[source]

Remove a widget from the children of this widget.

Parameters:
widget: Widget

Widget to remove from our children list.

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

The viewclass used by the current layout_manager.

viewclass is an AliasProperty that gets and sets the class used to generate the individual items presented in the view.

class kivy.uix.recycleview.RecycleViewBehavior(**kwargs)[source]

Bases: builtins.object

RecycleViewBehavior provides a behavioral model upon which the RecycleView is built. Together, they offer an extensible and flexible way to produce views with limited windows over large data sets.

See the module documentation for more information.

data_model

The Data model responsible for maintaining the data set.

data_model is an AliasProperty that gets and sets the current data model.

layout_manager

The Layout manager responsible for positioning views within the RecycleView.

layout_manager is an AliasProperty that gets and sets the layout_manger.

refresh_from_data(*largs, **kwargs)[source]

This should be called when data changes. Data changes typically indicate that everything should be recomputed since the source data changed.

This method is automatically bound to the on_data_changed method of the RecycleDataModelBehavior class and therefore responds to and accepts the keyword arguments of that event.

It can be called manually to trigger an update.

refresh_from_layout(*largs, **kwargs)[source]

This should be called when the layout changes or needs to change. It is typically called when a layout parameter has changed and therefore the layout needs to be recomputed.

refresh_from_viewport(*largs)[source]

This should be called when the viewport changes and the displayed data must be updated. Neither the data nor the layout will be recomputed.

view_adapter

The adapter responsible for providing views that represent items in a data set.

view_adapter is an AliasProperty that gets and sets the current view adapter.