Kivy 布局高度以适应子小部件的高度

Posted

技术标签:

【中文标题】Kivy 布局高度以适应子小部件的高度【英文标题】:Kivy Layout height to adapt to child widgets's height 【发布时间】:2015-12-07 17:59:40 【问题描述】:

我想创建一个布局,其中我有类似于 BoxLayout 的东西,让我能够在我的布局中创建“行”,并且在每个“行”中我想使用另一种 BoxLayout 中的东西来创建“列”。

列不需要均匀分布。例如,我想创建一个 BoxLayout,其中一列带有方形图像,另一列占据剩余的可用宽度。

在我的 gist 上查看代码和屏幕截图:https://gist.github.com/MichaelGradek/e5c50038b947352d9e79

我已经在上面的代码中完成了基本结构,但除此之外,我希望 BoxLayout 的高度适应孩子的高度。

实现这一目标的最佳方法是什么?

谢谢!

【问题讨论】:

【参考方案1】:

不要使用 BoxLayout,使用带有 height: self.minimum_height 的 GridLayout,并为每个子小部件设置手动大小(size_hint_y: Noneheight: some_number)。

【讨论】:

为什么同时需要 height 和 size_hint? @inclement,我认为你还需要在GridLayout 中添加size_hint_y: None,而不仅仅是在儿童中 @zwep,因为 kivy 优先于 size_hint 而不是 size,这意味着与直接像素大小相关的所有内容(包括 heightwidth)都将被忽略,除非对应的 size_hint 是设置为None。 Kivy 首先构建响应式,因此 size_hints 优先。【参考方案2】:

对我来说,使用高度为 self.minimum_height 的 GridLayout,然后为每个子小部件设置手动大小(size_hint_y:无和高度:some_number),导致小部件锚定到根窗口的底部。实在想不通为什么?

但是,使用高度为 root.height 的 GridLayout,然后为每个子窗口小部件设置手动大小(size_hint_y:无,高度:some_number)会产生正确的顶部锚定窗口小部件。

【讨论】:

【参考方案3】:

以下是我想出的一些技巧,用于根据孩子的高度设置GridLayout 高度,但是,每行都需要一个孩子...所以制作列确实需要添加内部网格布局。

# kv file or string
<Resizing_GridLayout@GridLayout>:
    cols: 1
    row_force_default: True
    foo: [self.rows_minimum.update(i: x.height) for i, x in enumerate(reversed(list(self.children)))]

可以通过 Python 代码或静态 kv 条目添加以上内容,并且在我的项目中至少可以自行调整包含的子小部件的每一行的大小。

# kv file or string for when above goes funky
<ResizingRow_GridLayout@GridLayout>:
    cols: 1
    height: sum([c.height for c in self.children])

为了完整起见,举个例子说明如何将它们缝合在一起......

# ... assuming above have been set, bellow maybe within another layout
Resizing_GridLayout:
    ResizingRow_GridLayout:
        Label:
            height: 30
            text: 'Label One'
        TextInput:
            height: 30
            multiline: False
            write_tab: False
            hint_text: 'Insert one liner'
    ResizingRow_GridLayout:
        Label:
            height: 45
            text: 'Label two'
        Button:
            text: 'Button One'
            height: 60
        GridLayout:
            rows: 1
            height: 25
            Button:
                text: 'Button Two'
            Button:
                text: 'Button three'

更新

modules/adaptive-grid-layout/__init__.py

#!/usr/bin/env python

from collections import OrderedDict
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock


class Adaptive_GridLayout(GridLayout):
    """
    Adaptive height and row heights for grid layouts.

    Note this should not be used as a root layout and '_refresh_grids_y_dimension()' method should be used by
    children widgets that change height to update all attached instances of Adaptive_GridLayout (this layout).

    Copyright AGPL-3.0 2019 S0AndS0
    """

    def __init__(self, grow_cols = False, grow_rows = False, **kwargs):
        super(Adaptive_GridLayout, self).__init__(**kwargs)
        self.grow_cols = grow_cols
        self.grow_rows = grow_rows
        self.trigger_refresh_y_dimension = Clock.create_trigger(lambda _: self._refresh_grids_y_dimension(), 0)

    def _yield_tallest_of_each_row(self):
        """ Yields tallest child of each row within gridlayout. """
        current_tallest = None
        for i, c in enumerate(list(reversed(self.children))):
            if current_tallest is None:
                current_tallest = c

            if c.height > current_tallest.height:
                current_tallest = c

            ## Should work around grids without value for 'cols'
            if self.cols is None or self.cols is 0:
                yield current_tallest
                current_tallest = None
            ## Reached last item of current row... Fizzbuzz!
            elif ((i + 1) % self.cols == 0) is True:
                yield current_tallest
                current_tallest = None

    def _calc_child_padding_y(self, child):
        """ Returns total padding for a given child. """
        ## May be faster than asking permission with an if statement as most widgets seem to have padding
        try:
            child_padding = child.padding
        except AttributeError as e:
            child_padding = [0]

        len_child_padding = len(child_padding)
        if len_child_padding is 1:
            padding = child_padding[0] * 2
        elif len_child_padding is 2:
            padding = child_padding[1] * 2
        elif len_child_padding > 2:
            padding = child_padding[1] + child_padding[3]
        else:
            padding = 0

        return padding

    def _calc_min_height(self):
        """ Returns total height required to display tallest children of each row plus spacing between widgets. """
        min_height = 0
        for c in self._yield_tallest_of_each_row():
            min_height += c.height + self._calc_child_padding_y(child = c) + self.spacing[1]
        return min_height

    def _calc_rows_minimum(self):
        """ Returns ordered dictionary of how high each row should be to accommodate tallest children of each row. """
        rows_minimum = OrderedDict()
        for i, c in enumerate(self._yield_tallest_of_each_row()):
            rows_minimum.update(i: c.height + self._calc_child_padding_y(child = c))
        return rows_minimum

    def _refresh_height(self):
        """ Resets 'self.height' using value returned by '_calc_min_height' method. """
        self.height = self._calc_min_height()

    def _refresh_rows_minimum(self):
        """ Resets 'self.rows_minimum' using value returned by '_calc_rows_minimum' method. """
        self.rows_minimum = self._calc_rows_minimum()

    def _refresh_grids_y_dimension(self):
        """ Updates 'height' and 'rows_minimum' first for spawn, then for self, and finally for any progenitors. """
        spawn = [x for x in self.walk(restrict = True) if hasattr(x, '_refresh_grids_y_dimension') and x is not self]
        for item in spawn:
            item._refresh_rows_minimum()
            item._refresh_height()

        self._refresh_rows_minimum()
        self._refresh_height()

        progenitors = [x for x in self.walk_reverse() if hasattr(x, '_refresh_grids_y_dimension') and x is not self]
        for progenitor in progenitors:
            progenitor._refresh_rows_minimum()
            progenitor._refresh_height()

    def on_children(self, instance, value):
        """ If 'grow_cols' or 'grow_rows' is True this will grow layout that way if needed instead of erroring out. """
        smax = self.get_max_widgets()
        widget_count = len(value)
        if smax and widget_count > smax:
            increase_by = widget_count - smax
            if self.grow_cols is True:
                self.cols += increase_by
            elif self.grow_rows is True:
                self.rows += increase_by
        super(Adaptive_GridLayout, self).on_children(instance, value)

    def on_parent(self, instance, value):
        """ Some adjustments maybe needed to get top row behaving on all platforms. """
        self.trigger_refresh_y_dimension()

以上内容来自我发布的一个项目,它来自一个更大的项目,可能适合也可能不适合那些希望在 Python 端使用更多逻辑来更新维度的人。检查ReadMe 文件以获取在另一个项目中安装的提示。

【讨论】:

如果 ResizingRow_GridLayout 包含一个动态添加子元素的小部件,这似乎不起作用。 这个didn't seem to help。我可能误解了你的意思。我需要了解self.calc_height() 是什么,然后再尝试更多complex case,其中动态添加的孩子可以改变大小。 @FergusWyrm 是的,我本可以更明确一点...检查更新并链接到我为跟踪此功能而发布的 GitHub 项目...在您的行内24 PasteBin 链接是一个问题点,Kivy kv 文件中的foo: stuff 是一种在实例化时运行某些东西的hackish 方式,尽管71 行可能会做类似的事情,真的应该是这样的东西一个 definition (AKA 类方法/函数)... 意思是 def calc_height(self):... 以便其他方法可以调用 self.cal_height()instance.cal_height() foo 工作正常。我让 foo 在构建器和类函数中表现相同。我确实制作了a small project,它更可靠地显示了这种奇怪的行为。尝试在 ResizingFrame 中禁用 self.trigger_refresh_y_dimension() 并将 lorum ipsum 粘贴到拉伸标签中。这应该按预期从孩子那里调用self.trigger_refresh_y_dimension()。然后你可以看到它只是将标签稍微向下移动。 我发现Adaptive_GridLayout 存在一些问题。 #1:尽管在_refresh_height() 中声明,但高度没有变化。 #2 padding 被错误地添加到任何大小估计中。填充是高度的一部分,不应考虑在内。将self.size_hint_y = None 添加到__init__() 修复了问题#1。我必须准确找到尺寸被错误计算的位置以解决问题 #2。我怀疑还有一个与rows_minimum 相关的未诊断问题。

以上是关于Kivy 布局高度以适应子小部件的高度的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Kivy 中设置小部件/布局的最小允许宽度/高度?

如何更轻松地实现一个 Flutter 布局?

在子小部件中,如何在 kivy 中获取父小部件的实例

Kivy 如何访问子小部件中的小部件

子部件在 Dojo 中不会渲染(高度为 0)

PySide QScrollArea 以编程方式滚动到特定的子小部件