Search code examples
pythonkivykivymd

Fix column width in RecycleView when changing density


I'm working with multiple columns in RecycleView. For the sake of simplification, the following example contains only a ref column which I expect to be displayed on one line and a message column that should take the rest of the screen, the text being wrapped in order to be fully readable. Both columns are implemented using MDLabel instances:

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.properties import StringProperty
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.label import MDLabel
from random import randrange
from loremipsum import get_sentences


Builder.load_string('''
<RVLayout>:
    spacing: 2
    size_hint: None, None
    height: message.texture_size[1]
    MDLabel:
        size_hint_x: None # label width should fit text
        halign: 'center'
        text: root.ref
        md_bg_color: app.theme_cls.bg_darkest
    MDLabel:
        id: message
        text: root.message
        md_bg_color: app.theme_cls.bg_darkest

<RV>:
    viewclass: 'RVLayout'
    RecycleBoxLayout:
        spacing: 2
        default_size: None, dp(10)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'
''')

class RVLayout(BoxLayout):
    ref = StringProperty()
    message = StringProperty()

    def on_size(self, *args):
        self.height = self.ids.message.texture_size[1]

class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.data = [
            {
                'ref': str(x).zfill(8), 
                'message': ' '.join(get_sentences(randrange(1, 4)))
            } for x in range(50)]

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

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

As you can see, I wrote an on_size method to my RVLayout so that everything looks well on resize (desktop case):

enter image description here

The height of the message elements fits perfectly for any window width, while the width of ref stays the same:

enter image description here

Now here comes my issue, when I start my app with a density > 1, the ref column width is still the same even though the font is bigger. For instance with the environment variable KIVY_METRICS_DENSITY=2 I get the following:

enter image description here

How can I make ref width adapt to the density?


Solution

  • Using adaptive size like suggested by @JohnAnderson put me on the right track. The sole problem is that the size is adapted on x and y axis. Thus the ref labels are not the same height as the message ones, which doesn't look great:

    enter image description here

    Playing with size_hint_y and size_y didn't help until I switched from BoxLayout to GridLayout and set size_hint_y: root.height:

    from kivymd.app import MDApp
    from kivy.lang import Builder
    from kivy.properties import StringProperty
    from kivy.uix.recycleview import RecycleView
    from kivy.uix.gridlayout import GridLayout
    from kivymd.uix.label import MDLabel
    from random import randrange
    from loremipsum import get_sentences
    
    
    Builder.load_string('''
    <RVLayout>:
        cols: 2
        spacing: 2
        size_hint: None, None
        height: message.texture_size[1]
        MDLabel:
            adaptive_size: True
            size_hint_y: root.height
            padding_x: dp(5)
            halign: 'center'
            text: root.ref
            md_bg_color: app.theme_cls.bg_darkest
        MDLabel:
            id: message
            text: root.message
            md_bg_color: app.theme_cls.bg_darkest
    
    <RV>:
        viewclass: 'RVLayout'
        RecycleBoxLayout:
            spacing: 2
            default_size: None, dp(10)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
    ''')
    
    class RVLayout(GridLayout):
        ref = StringProperty()
        message = StringProperty()
    
        def on_size(self, *args):
            self.height = self.ids.message.texture_size[1]
    
    class RV(RecycleView):
        def __init__(self, **kwargs):
            super(RV, self).__init__(**kwargs)
            self.data = [
                {
                    'ref': str(x).zfill(8), 
                    'message': ' '.join(get_sentences(randrange(1, 4)))
                } for x in range(50)]
    
    class TestApp(MDApp):
        def build(self):
            return RV()
    
    if __name__ == '__main__':
        TestApp().run()
    

    pos_hint isn't needed anymore in this case.

    Now things look great for any density:

    density==1:

    enter image description here

    density==2:

    enter image description here

    Edit: using adaptive_width: True with BoxLayout works too