Search code examples
pythonkivykivymd

How to make Labels in RecycleView change size according to texture size during runtime?


I have a RecycleView which consists of labels and a MDSlider which I use to change font_size of those labels during runtime. I'm wondering if it's possible to align labels' size to their texture_size property. Precisely, whenever I change the font_size which would cause label's texture_size to overflow its current size, I would want to increase the label size so the text fits. Since labels live inside the RecycleView, I'm not sure how should I approach the problem. I'm initially setting height to self.minimum_height of the RecycleBoxLayout which I presume should be updated in the process. I noticed that I can manually change the RecycleBoxLayout's default_size during runtime, but not sure how exactly I should pass the texture_size of the label to adjust the size. I tried with on_size and on_texture methods to pass the property and use to calculate default_size, but things get really complicated and I always end up getting gaps between the labels. Ideally I would want the solution that uses some kind of binding of labels sizes/texture (similarly like I already have with the app.fontSize) so I would get automatic resizing because any manual calculations of RecycleView properties and it's consequential update significantly slows down my program in the end when testing on Android.

Any ideas?

EDIT: I haven't mentioned it, I'm only interested in the height resizing. Width doesn't matter.

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen
from kivymd.uix.boxlayout import MDBoxLayout
from kivy.properties import StringProperty

kv = """
<MyLabel@MDLabel>:
    font_size: app.fontSize
    halign: 'center'
    # Using these settings would be awesome, but cannot make it happen.
    # Or there might be more elegant solution?
    #size: self.texture_size
    #size_hint_y: None
    #text_size: self.width, None
    

<DailyService>:
    day: ''
    service: ''
    
    MDGridLayout:
        rows: 2
        MyLabel:
            id: firstLabelId
            text: root.day
            md_bg_color: app.theme_cls.accent_color

        MyLabel:
            id: secondLabelId
            md_bg_color: app.theme_cls.primary_dark
            text: root.service
    
<MainScreen>:
    name: 'mainScreen'
    myRv: rvId

    MDRelativeLayout:
        orientation: 'vertical'

        MDRecycleView:
            viewclass: 'DailyService'
            id: rvId
            rbl: rblId
            
            RecycleBoxLayout:
                id: rblId
                default_size: None, dp(200)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'

        MDSlider:
            color: 'white'
            orientation: 'horizontal'
            size_hint: (0.2, 0.2)
            pos_hint: {"x":0.4, "top": 1}
            min: 10
            value: 20
            max: 150
            on_value_normalized: root.fontSizeSlider(self.value)


MyScreenManager:
    mainScreen: mainScreenId
        
    MainScreen:
        id: mainScreenId
"""

class DailyService(MDBoxLayout):
    pass

class MainScreen(Screen):
    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)

    def fontSizeSlider(self, value):
        app = MDApp.get_running_app()
        app.fontSize = str(int(value)) + 'dp'
        self.myRv.refresh_from_data()
    
class MyScreenManager(ScreenManager):    
    def __init__(self, **kwargs):
        super(MyScreenManager, self).__init__(**kwargs)


class MyApp(MDApp):
    fontSize = StringProperty('20dp')
    
    def on_start(self):
        data = []
        for i in range(10):
            data.append({'day': 'DAY\nDAY',
                         'service': 'SERVICE\nSERVICE'})
        self.root.ids.mainScreenId.myRv.data = data

    def build(self):
        self.theme_cls.theme_style = 'Dark'
        self.theme_cls.primary_palette = 'Blue'
        self.theme_cls.accent_palette = 'Amber'
        return Builder.load_string(kv)

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

Solution

  • from kivymd.app import MDApp
    from kivy.lang import Builder
    from kivy.uix.screenmanager import ScreenManager
    from kivy.uix.screenmanager import Screen
    from kivymd.uix.boxlayout import MDBoxLayout
    from kivy.properties import StringProperty
    
    kv = """
    <MyLabel@MDLabel>:
        font_size: app.fontSize
        halign: 'center'
        # Using these settings would be awesome, but cannot make it happen.
        # Or there might be more elegant solution?
        #size: self.texture_size
        #size_hint_y: None
        #text_size: self.width, None
        
    
    <DailyService>:
        day: ''
        service: ''
        
        MDGridLayout:
            rows: 2
            size_hint_y:None
            height:app.MyHeight
            MyLabel:
                id: firstLabelId
                text: root.day
                md_bg_color: app.theme_cls.accent_color
    
            MyLabel:
                id: secondLabelId
                md_bg_color: app.theme_cls.primary_dark
                text: root.service
        
    <MainScreen>:
        name: 'mainScreen'
        myRv: rvId
    
        MDRelativeLayout:
            orientation: 'vertical'
    
            MDRecycleView:
                viewclass: 'DailyService'
                id: rvId
                rbl: rblId
                
                RecycleBoxLayout:
                    id: rblId
                    default_size: None, app.MyHeight
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
    
            MDSlider:
                color: 'white'
                orientation: 'horizontal'
                size_hint: (0.2, 0.2)
                pos_hint: {"x":0.4, "top": 1}
                min: 10
                value: 20
                max: 150
                on_value_normalized: root.fontSizeSlider(self.value)
    
    
    MyScreenManager:
        mainScreen: mainScreenId
            
        MainScreen:
            id: mainScreenId
    """
    
    class DailyService(MDBoxLayout):
        pass
    
    class MainScreen(Screen):
        def __init__(self, **kwargs):
            super(MainScreen, self).__init__(**kwargs)
    
        def fontSizeSlider(self, value):
            app = MDApp.get_running_app()
            app.fontSize = str(int(value)) + 'dp'
            app.MyHeight = str(  int(value) * 10 ) + 'dp'
            self.myRv.refresh_from_data()
        
    class MyScreenManager(ScreenManager):    
        def __init__(self, **kwargs):
            super(MyScreenManager, self).__init__(**kwargs)
    
    
    class MyApp(MDApp):
        fontSize = StringProperty('20dp')
        MyHeight = StringProperty('200dp')
        def on_start(self):
            data = []
            for i in range(10):
                data.append({'day': 'DAY\nDAY',
                             'service': 'SERVICE\nSERVICE'})
            self.root.ids.mainScreenId.myRv.data = data
    
        def build(self):
            self.theme_cls.theme_style = 'Dark'
            self.theme_cls.primary_palette = 'Blue'
            self.theme_cls.accent_palette = 'Amber'
            return Builder.load_string(kv)
    
    if __name__ == '__main__':
        MyApp().run()