Kivy 布局高度以适应 child 小部件的高度
Kivy Layout height to adapt to child widgets's height
我想创建一个布局,其中我有类似于 BoxLayout 的东西,让我能够在我的布局中创建 "rows",并且在每个 "row" 中我想使用一些东西另一种 BoxLayout 来创建 "columns".
列不需要均匀分布。例如,我想创建一个 BoxLayout,其中一列带有方形图像,另一列占据剩余的可用宽度。
查看我的要点中的代码和屏幕截图:https://gist.github.com/MichaelGradek/e5c50038b947352d9e79
我上面的代码已经完成了基本结构,但是另外,我想让BoxLayout的高度适应children的高度。
实现此目标的最佳方法是什么?
谢谢!
不要使用 BoxLayout,使用带有 height: self.minimum_height
的 GridLayout,并为每个子窗口小部件设置手动大小(size_hint_y: None
和 height: some_number
)。
这是我想出的一些技巧,用于根据子项高度设置 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
文件以获取有关在另一个项目中安装的提示。
对我来说,使用高度为 self.minimum_height 的 GridLayout,然后为每个 size_hint_y:None 和高度:some_number 设置手动大小=20=] 小部件,导致小部件锚定到根 window 的底部。真的不知道为什么?
但是,使用高度为 root.height 的 GridLayout,然后为每个 [=] 设置手动大小(size_hint_y:None 和高度:some_number) 20=] 小部件导致正确的顶部锚定小部件。
我想创建一个布局,其中我有类似于 BoxLayout 的东西,让我能够在我的布局中创建 "rows",并且在每个 "row" 中我想使用一些东西另一种 BoxLayout 来创建 "columns".
列不需要均匀分布。例如,我想创建一个 BoxLayout,其中一列带有方形图像,另一列占据剩余的可用宽度。
查看我的要点中的代码和屏幕截图:https://gist.github.com/MichaelGradek/e5c50038b947352d9e79
我上面的代码已经完成了基本结构,但是另外,我想让BoxLayout的高度适应children的高度。
实现此目标的最佳方法是什么?
谢谢!
不要使用 BoxLayout,使用带有 height: self.minimum_height
的 GridLayout,并为每个子窗口小部件设置手动大小(size_hint_y: None
和 height: some_number
)。
这是我想出的一些技巧,用于根据子项高度设置 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
文件以获取有关在另一个项目中安装的提示。
对我来说,使用高度为 self.minimum_height 的 GridLayout,然后为每个 size_hint_y:None 和高度:some_number 设置手动大小=20=] 小部件,导致小部件锚定到根 window 的底部。真的不知道为什么?
但是,使用高度为 root.height 的 GridLayout,然后为每个 [=] 设置手动大小(size_hint_y:None 和高度:some_number) 20=] 小部件导致正确的顶部锚定小部件。