GawronDev
GawronDev

Reputation: 137

Kivy (Python): How to change the BoxLayout height based on its children?

Im writing a news app in Kivy that takes articles from a front page of a website and makes them viewable in the app. Depending on the articles length the size and number of paragraphs may vary. Since it uses a BS4 webscraper to exctract the contents of a website, the whole gui programming takes place in python, not in .kv language. So, my question is, how can I stop the Image from reserving space on screen (I want the labels to always be under it) and how can I change the height of a BoxLayout based on its children so there is enough space for all of them, but not too much.

Here is a simplified version of my code that demonstrates my problem. In this example the height of a BoxLayout is set to 1000dp which is fine for roughly 4 paragraphs but if there were to be more it would become to cluttered. Is there a way to avoid this problem? Any help will be useful.

from kivymd.app import MDApp
from kivy.uix.boxlayout import BoxLayout
from kivymd.uix.label import MDLabel
from kivy.uix.screenmanager import Screen
from kivy.uix.scrollview import ScrollView
from kivy.uix.image import AsyncImage


class RootWidget(Screen):
    def __init__(self, **kw):
        super().__init__(**kw)
        self.update()

    def update(self):
        paragraphs_text = ["Lorem ipsum dolor sit amet,", "consectetur adipiscing elit, sed do eiusmod tempor incididunt ut"
                      " labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco "
                      "laboris nisi ut aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehenderit in"
                      " voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat "
                      "non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."]

        header_text = "Lorem ipsum dolor sit amet"

        header_image_src = "images/Cariboo_Peaks.jpg"

        date_of_publishing_text = "03.05.2020"

        scroll_widget_holder = ScrollView()
        widget_holder = BoxLayout(orientation="vertical", size_hint_y=None, padding=5)

        # minimum_height doesnt work in python, only in .kv language so is there a way to get the height automatically?
        # 1000dp works fine for this amount of paragrpahs but i there was twice as many as there are now it would all
        # become to cludderd and the image would be very small
        # on the other hand, if I make this number too big the wigets are too far apart from each other
        # So how can I fix this?
        widget_holder.height = "1000dp"

        header_image = AsyncImage(source=header_image_src, size_hint=(1, 1),
                                  allow_stretch=False, keep_ratio=True)

        header_label = MDLabel(text=header_text, size_hint_y=None, font_style="H6")

        # Thats what I came up with, if there is a better way of automatically asigning the height of a widget based
        # on its contents please let me know
        header_label.height = str(len(header_label.text)) + "dp"

        # This label is supossed to be right unter the header label(see the screens below to see my go to result created
        # in photoshop)
        header_date_label = MDLabel(text=date_of_publishing_text, size_hint_y=None, font_style="Caption", height="10dp")

        widget_holder.add_widget(header_image)
        widget_holder.add_widget(header_label)
        widget_holder.add_widget(header_date_label)

        for paragraph in paragraphs_text:
            text_paragraph = MDLabel(text=paragraph, size_hint_y=None,
                                     pos_hint={"center_x": .5}, halign="justify")
            text_paragraph.height = str(len(text_paragraph.text)) + "dp"

            widget_holder.add_widget(text_paragraph)

        scroll_widget_holder.add_widget(widget_holder)
        self.add_widget(scroll_widget_holder)


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


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

Upvotes: 0

Views: 1428

Answers (1)

John Anderson
John Anderson

Reputation: 39092

You can calculate the height of the BoxLayout yourself by just adding the heights of all its children (including padding and spacing). If you know (or set) the heights of the children as you add them during the update method, you can just set the height using:

widget_holder.height = new_height

where new_height is the above calculated sum. If you don't know the heights of the children, you can do something like:

def adjust_height(self, box, dt):
    # this does not handle padding and spacing
    new_height = 0
    for child in box.children:
        new_height += child.height
    box.height = new_height

And you can call it at the end of your update() method as:

    Clock.schedule_once(partial(self.adjust_height, widget_holder))

Upvotes: 1

Related Questions