pythonbindingkivykivymdkivy-recycleview

Kivy, Recycleview with custom widget


I made a recycleview list of custom cards. For each card i want to pass a custom object called "show", its type is "Content". The class of custom card is "StreamingShowCard". I can't create the card by KVlang because its class contains some pytho methods.

This is the code:

from kivymd.app import MDApp
from kivy.lang import Builder
from kivymd.uix.behaviors import RoundedRectangularElevationBehavior
from kivymd.uix.card import MDCard
from kivy.properties import StringProperty
from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.list import OneLineIconListItem
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.label import MDLabel
from kivymd.uix.gridlayout import MDGridLayout
from kivymd.uix.fitimage import FitImage
from kivy.uix.image import AsyncImage
from kivymd.uix.button import MDFlatButton
#import datetime
from datetime import datetime
from kivy.uix.image import Image
from kivymd.uix.dialog import MDDialog
from kivymd.uix.screen import MDScreen
from kivymd.uix.responsivelayout import MDResponsiveLayout
from kivymd.uix.progressbar import MDProgressBar
from kivy.clock import Clock
from kivymd.uix.button import MDIconButton
from kivymd.uix.list import IconLeftWidget
from kivymd.uix.recycleview import MDRecycleView
from kivy import properties


KV = '''

<Cover>:


<StreamingShowCard>:
    show: root.show



MDScreen:
    MDRecycleView:
        size_hint_y: 1
        size_hint_x: 1
        viewclass: 'StreamingShowCard'
        id: rv
        
        MDRecycleGridLayout:
            cols: 2
            height: self.minimum_height
            size_hint_y: None
            row_default_height: '250dp'

            row_force_default: True

            padding: dp(10)
            spacing: dp(10)

            

    MDFloatingActionButton:
        id: fab
        icon: "plus"
        pos_hint: {"center_x": .5, "center_y": .1}
        on_release: app.add_item()


'''
class MD3Card(MDCard, RoundedRectangularElevationBehavior):
    '''Implements a material design v3 card.'''

    text = StringProperty()

class Content: #eg: film found
    name = ""
    url = ""
    platform = ""
    imageUrl = ""
    Image = False
    #free = False
    def __init__(self, name, url, platform, imageUrl):
        self.name = name
        self.url = url
        self.platform = platform
        self.imageUrl = imageUrl

class StreamingShowCard(MD3Card):
    #show = None #the object of Content class attached to the 
    show = properties.ObjectProperty()
    def __init__(self, **kwargs):
        super(StreamingShowCard, self).__init__(**kwargs)
        self.__set_cardGraphic()
        
        self.image = Cover(size_hint_min_y=0.7,  source="https://kivy.org/doc/stable/_static/logo-kivy.png", opacity= 100 if True else 0, radius= ["10dp", "10dp", "0dp", "0dp"])
        grid1 = MDGridLayout(cols=1, size_hint_x=1, size_hint_y=1, rows=3)
        grid1.add_widget(self.image)
        grid1.add_widget(MDLabel(text=self.show, size_hint_y = 0.3, font_style='Subtitle2', halign='left', valign='middle'))
        
        box1 = MDBoxLayout(orientation = "horizontal", size_hint_x=1, size_hint_y=0.2)
        box1.add_widget(MDLabel(text="self.show.platform", size_hint_y = 1, size_hint_x=0.8, theme_text_color="Secondary", font_style='Caption', halign='left', valign='middle'))
        

        grid1.add_widget(box1)
        self.add_widget(grid1)
        
    def __set_cardGraphic(self):
        self.md_bg_color = "#18222c"
        #self.size_hint_y = None
        self.elevation = 10
        self.padding = "1dp"
        self.size_hint_x = 1
        self.radius = "10dp"
        self.ripple_behavior = True
        self.on_press = self.cardPress
    
    def cardPress(self, *args):
        dialog = MDDialog(
            title = "Aprire la pagina in un browser?",
            text = "Text",#self.show.name,
            size_hint = (0.8, 0.8),
            auto_dismiss = False,
            buttons=[
                MDFlatButton(text="No", text_color=self.theme_cls.primary_color, on_release=lambda x: dialog.dismiss()),
                MDFlatButton(text="Si", text_color=self.theme_cls.primary_color, on_release=lambda x: yesClick())
            ]
        )
        dialog.open()

        def yesClick():
            #openUrl(self.show.url)
            dialog.dismiss(force=True)

    def loadImage(self, *args):
        if not self.show.Image:
            self.show.loadImage(True)
            self.image.source = self.show.imageUrl
            self.image.opacity = 100
            self.image.reload()
    

class Cover(AsyncImage, FitImage):
    pass


class MainApp(MDApp):
    def build(self):
        return Builder.load_string(KV)

    def add_item(self):
        for i in range(100):
            self.root.ids.rv.data.append({
                "show": Content("name", "www.google.com", "platform", "https://kivy.org/doc/stable/_static/logo-kivy.png")
            })

MainApp().run()

If the recycleview's viewclass is a Label i can pass the text directly from main by appending dict to RV data, but whit this custom class of recycle view i cannot pass Content to show property. Is it possible to populate the RecycleView by the main?


Solution

  • Couldn't get your code to run, but if you want to use an ObjectProperty in the data of a RecycleView, you need to handle it a bit differently. Here is an example of using an ObjectProperty in the data:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.properties import ObjectProperty, StringProperty
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.image import Image
    from kivy.uix.recycleview import RecycleView
    
    Builder.load_string('''
    <MyObject>:
        size_hint_y: None
        height: 100
        Label:
            id: label
            text: root.text  # uses the text StringProperty
            size_hint: None, None
            size: 200, 100
    <RV>:
        viewclass: 'MyObject'
        RecycleBoxLayout:
            default_size: None, 100
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
    ''')
    
    
    class MyObject(BoxLayout):
        show = ObjectProperty(None)
        text = StringProperty('Abba')
    
        def on_show(self, instance, new_obj):
            # handle the ObjectProperty named show
            if new_obj.parent:
                # remove this obj from any other MyObject instance
                new_obj.parent.remove_widget(new_obj)
            for ch in self.children:
                if isinstance(ch, Image):
                    # remove any previous obj instances
                    self.remove_widget(ch)
                    break
            # add the new obj to this MyObject instance
            self.add_widget(new_obj)
    
    
    class RV(RecycleView):
        def __init__(self, **kwargs):
            super(RV, self).__init__(**kwargs)
            self.data = [{'text': str(x),
                          'show': Image(source='tester.png', size_hint=(None, None), size=(100, 100),
                                        allow_stretch=True, keep_ratio=True)}
                         for x in range(100)]
    
    
    class TestApp(App):
        def build(self):
            return RV()
    
    if __name__ == '__main__':
        TestApp().run()