How do i add custom widgets to RecycleView ??

809 views
Skip to first unread message

Xero ___

unread,
Mar 7, 2022, 1:45:56 PM3/7/22
to Kivy users support

I have no clue how to work with recycle view but that is the only way i can optimise my application . I was using scrollview to add some widgets which had text from a text file . The more the values in text file , more time it takes to make widgets in the scroll view .
It is taking about 9 seconds to iterate and add 15 widgets from text file containing 15 sentences. I have provided an example below but in the main code  I am doing much more with custom_box widget like using regex to add time and date at specific parts of widgets.

here is an example



from kivy.core.window import Window
from kivy.lang.builder import Builder
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.properties import StringProperty
from kivy.uix.recycleview import RecycleView

Window.size = (500, 500)
kv = '''
MDBoxLayout:
orientation: "vertical"
MDBoxLayout:
size_hint_y: None
id: a1
height: 50
MDIconButton:
id: b1
icon:"arrow-left"
on_press: app.pressed_back_button() if b1.icon == "arrow-left" else msglist.unselected_all()
on_release: app.critical_function() if b1.icon == "arrow-left" else msglist.unselected_all() # in my main app , some backend functions depend heavily on this , but its working with unselected so its not ideal
MDIconButton:
id: b2
icon:"camera"
MDBoxLayout:
size_hint_y: None
height: 50
spacing: 5
padding: [0, 0, 10, 0]
MDBoxLayout:
id: scroll
MDSelectionList: #-----------------> here if its changed to MDList , everything works fine
id: msglist
spacing: 10
size_hint_y:None
height: self.minimum_height
on_selected: app.selected(*args)
on_unselected: app.unselected(*args)
on_selected_mode: app.set_selection_mode(*args)
MDBoxLayout:
padding: [10, ]
spacing: 5
size_hint_y:None
height: 80
MDTextField:
id: text
multiline:True
MDIconButton:
icon: "send"
on_press: app.push_text(text.text)

<Custom_Box>:
id: chtbld
md_bg_color: [0, 0, 0, 0]
size_hint: None, None
size: msg_content.size
padding: [10, 0, 10, 0]
orientation: 'vertical'
MDBoxLayout:
id: box
size:msg_content.size
size_hint: None, None
md_bg_color: app.theme_cls.bg_darkest
MDRelativeLayout:
id: boxT
MDLabel:
id: msg_content
text: root.msg
font_size:15
size_hint: None, None
size: self.texture_size
# fixed the width that where the text wraps, this should be a more complex function
text_size: 400 if len(self.text)>40 else len(self.text) * 10, None
halign: 'left'
color: app.theme_cls.opposite_bg_normal
<RV>:
viewclass:"Custom_Box"
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
'''

# in my app , back arrow changes screens and buth on_press and on_release have been appointed to do some critical jobs to make my app function properly

class RV(RecycleView):
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.msg = Custom_Box()
with open('icon.txt','r') as file:
file = file.read()
for i in file:
self.msg = Custom_Box()
# self.msg.ids["msg_content"].text = text # This overwrites the binding in kv... use the msg property
self.msg.msg = i
self.data = [self.msg]

class Custom_Box(MDBoxLayout):
msg = StringProperty()
time = StringProperty()


class SelectionListTest(MDApp):
def build(self):
GUI = Builder.load_string(kv)
return GUI
def on_start(self):
self.rv = RV()
self.root.ids.scroll.add_widget(self.rv)
with open('icon.txt','r') as file:
file = file.read()
for i in file:
self.msg = Custom_Box()
# self.msg.ids["msg_content"].text = text # This overwrites the binding in kv... use the msg property
self.msg.msg = i # updateing the msg property
#self.msg.ids.msg_content.texture_update() # Update the texture
#self.rv.add_widget(self.msg)

def push_text(self, text):
self.rv =RV()
self.msg = Custom_Box()
self.root.ids["text"].text = ""
# self.msg.ids["msg_content"].text = text # This overwrites the binding in kv... use the msg property
self.msg.msg = text # updateing the msg property
self.msg.ids.msg_content.texture_update() # Update the texture
self.rv.add_widget(self.msg)

def pressed_back_button(self):
print("pressed back button")

def critical_function(self):
print("critical task")

def set_selection_mode(self, instance_selection_list, mode):
if mode:
self.root.ids["b1"].icon = "alpha-x-box"
self.root.ids["b2"].icon = "trash-can"
else:
self.root.ids["b1"].icon = "arrow-left"
self.root.ids["b2"].icon = "camera"

def selected(self, instance_selection_list, instance_selection_item):
print('selected list:', end=' ')
items = instance_selection_list.get_selected_list_items() # get this list items
for w in items:
print(w.children[1].msg, end=' ') # childern[1] is Custom_Box
print()
print(f'Selected item: {instance_selection_item.children[1].msg}')
# self.root.ids["b2"].bind(on_press = lambda x : self.root.ids["msglist"].remove_widget(instance_selection_item))
#print(self.msg(instance_selection_item).msg_content.text)
# i was also trying to get text of some specific widget text , but i didnt get any proper documentation to assist me...

def unselected(self, instance_selection_list, instance_selection_item):
print(f'Unselected item: {instance_selection_item.children[1].msg}')

if __name__ == "__main__":
SelectionListTest().run()

its a crappy example but all i am trying to do is add widgets to RecycleView , but it shows an error saying Custom_Box has no attribute get .
Its my first project with kivy so i dont know too much about kivy , but I am still learning. Thank you for your time.

Elliot Garbus

unread,
Mar 7, 2022, 4:41:46 PM3/7/22
to kivy-...@googlegroups.com

Here is an example of how to create a Recycleview with custom widgets.  The comments in the code describe how the elements work together.

 

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


kv =
'''
<TwoButtons>:
# This class is used as the viewclass in the RecycleView
# The means this widget will be instanced to view one element of data from the data list.
# The RecycleView data list is a list of dictionaries.  The keys in the dictionary specify the
# attributes of the widget.
    Button:
       text: root.left_text
        on_release: print(f'Button {self.text} pressed')
    Button:
        text: root.right_text
        on_release: print(f'Button {self.text} pressed')
        
BoxLayout:
    orientation: 'vertical'
    Button:
        size_hint_y: None
        height: 48
        text: 'Add widget to RV list'
        on_release: rv.add()
   
    RV:                          # A Reycleview
        id: rv
        viewclass: 'TwoButtons'  # The view class is TwoButtons, defined above.
        data: self.rv_data_list  # the data is a list of dicts defined below in the RV class.
        scroll_type: ['bars', 'content']
        bar_width: 10
        RecycleBoxLayout:       
            # This layout is used to hold the Recycle widgets
            default_size: None, dp(48)   # This sets the height of the BoxLayout that holds a TwoButtons instance.

            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height   # To scroll you need to set the layout height.
            orientation: 'vertical'
'''


class TwoButtons(BoxLayout):        # The viewclass definitions, and property definitions.
   
left_text = StringProperty()
    right_text = StringProperty()


class RV(RecycleView):
    rv_data_list = ListProperty()    
# A list property is used to hold the data for the recycleview, see the kv code

   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
       
self.rv_data_list = [{'left_text': f'Left {i}', 'right_text': f'Right {i}'} for i in range(2)]
       
# This list comprehension is used to create the data list for this simple example.
        # The data created looks like:
        # [{'left_text': 'Left 0', 'right_text': 'Right 0'}, {'left_text': 'Left 1', 'right_text': 'Right 1'},
        # {'left_text': 'Left 2', 'right_text': 'Right 2'}, {'left_text': 'Left 3'},...
        # notice the keys in the dictionary correspond to the kivy properties in the TwoButtons class.
        # The data needs to be in this kind of list of dictionary formats.  The RecycleView instances the
        # widgets, and populates them with data from this list.

   
def add(self):
        l =
len(self.rv_data_list)
       
self.rv_data_list.extend([{'left_text': f'Added Left {i}', 'right_text': f'Added Right {i}'} for i in range(l,l+1)])


class RVTwoApp(App):

   
def build(self):
       
return Builder.load_string(kv)


RVTwoApp().run()

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/f0cd7a52-8fd5-40af-9e58-033dbe1eedabn%40googlegroups.com.

 

Xero ___

unread,
Mar 8, 2022, 6:22:02 AM3/8/22
to kivy-...@googlegroups.com
I just figured out a problem , with recycleview , I can reuse only one widget , therefore I can't use MDSelectionList . So using recycleview isn't that good in my case as I want to be able to do much more than just making widgets . Any alternative if recycleview which can act like ScrollView ?

Is it possible to generate widgets while scrolling down in ScrollView ? Just like widget creation in apps like whatsapp ??

Elliot Garbus

unread,
Mar 8, 2022, 9:31:53 AM3/8/22
to kivy-...@googlegroups.com

You can have widgets of different types in one RecycleView using the key_viewclass attribute.

Under you kivy install you can find the kivy-examples directory (if you installed it) located at… \share\kivy-examples\widgets\recycleview\key_viewclass.py

There are a number of RV examples.  Below is the example using the key_viewclass.

 

'''
A form generator, using random data, but can be data driven (json or whatever)

Shows that you can use the key_viewclass attribute of RecycleView to select a
different Widget for each item.
'''

from random import choice, choices
from string import ascii_lowercase

from kivy.app import App
from kivy.lang import Builder

from kivy import properties as P


KV =
r'''
<RVTextInput,RVCheckBox,RVSpinner>:
    size_hint_y: None
    height: self.minimum_height
    index: None
    title: ''


<RVTextInput@BoxLayout>:
    value: ''
    Label:
        text: root.title
        size_hint_y: None
        height: self.texture_size[1]
    TextInput:
        text: root.value
        on_text: app.handle_update(self.text, root.index)
        size_hint_y: None
        height: dp(40)
        multiline: False


<RVCheckBox@BoxLayout>:
    value: False
    Label:
        text: root.title
        size_hint_y: None
        height: self.texture_size[1]
    CheckBox:
        active: root.value
        on_active: app.handle_update(self.active, root.index)
        size_hint_y: None
        height: dp(40)


<RVSpinner@BoxLayout>:
    value: ''
    values: []
    Label:
        text: root.title
        size_hint_y: None
        height: self.texture_size[1]
    Spinner:
        text: root.value
        values: root.values
        size_hint_y: None
        height: dp(40)
        on_text: app.handle_update(self.text, root.index)


FloatLayout:
    RecycleView:
        id: rv
        data: app.data
        key_viewclass: 'widget'
        size_hint_x: 1
        RecycleBoxLayout:
            orientation: 'vertical'
            size_hint_y: None
            height: self.minimum_height
            default_size_hint: 1, None

'''


class Application(App):
   
'''A form manager demonstrating the power of RecycleView's key_viewclass
    property.
    '''
   
data = P.ListProperty()

   
def build(self):
        root = Builder.load_string(KV)
        rv = root.ids.rv
       
self.data = [
           
self.create_random_input(rv, index)
            
for index in range(20)
        ]

       
return root

   
def handle_update(self, value, index):
       
if None not in (index, value):
           
self.data[index]['value'] = value

   
def create_random_input(self, rv, index):
       
return choice((
           
self.create_textinput,
           
self.create_checkbox,
           
self.create_spinner
        ))(rv, index)

   
def create_spinner(self, rv, index):
       
"""
        create a dict of data for a spinner
        """
       
return {
            
'index': index,
           
'widget': 'RVSpinner',
           
'value': '',
           
'values': [
                letter *
5
               
for letter in ascii_lowercase[:5]
            ],
           
'ready': True,
        }

   
def create_checkbox(self, rv, index):
       
"""
        create a dict of data for a checkbox
        """
       
return {
           
'index': index,
           
'widget': 'RVCheckBox',
           
'value': choice((True, False)),
           
'title': ''.join(choices(ascii_lowercase, k=10)),
           
'ready': True,
        }

   
def create_textinput(self, rv, index):
       
"""
        create a dict of data for a textinput
        """
       
return {
           
'index': index,
           
'widget': 'RVTextInput',
           
'value': ''.join(choices(ascii_lowercase, k=10)),
           
'title': ''.join(choices(ascii_lowercase, k=10)),
           
'ready': True,
        }


if __name__ == "__main__":
    Application().run()

Andrew Fielden

unread,
Jun 24, 2024, 8:37:07 PM (8 days ago) Jun 24
to Kivy users support

Thank you to for this example. I've recently started to use Kivy, and have been looking for a useful example of RecycleView which uses a viewclass with customised row layout.
Your example with two Buttons is just what I was looking for, and I can adapt it.
Reply all
Reply to author
Forward
0 new messages