kivy 之 布局(上)

112 阅读9分钟

kivy最新版2.3.1 (截止2025年上半年), 其提供的布局类有如下这些:

  1. GridLayout
  2. PageLayout
  3. BoxLayout
  4. StackLayout
  5. AnchorLayout
  6. FloatLayout
  7. RelativeLayout
  8. ScatterLayout

布局(上)我们讨论前4个布局类

1.GridLayout(网格布局)

表现形式为多行多列的矩阵.每个被添加的组件都是自动从左向右, 从上向下依次被添加的.

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput

# 创建一个集成了GridLayout的自定义类
class MyGridLayout(GridLayout):

    # 初始化参数
    def __init__(self, **kwargs):
        super(MyGridLayout, self).__init__(**kwargs)

        self.cols = 2
        self.rows = 2
        self.add_widget(Label(text="UserName:"))
        self.add_widget(TextInput(multiline=False))
        self.add_widget((Label(text='Password:')))
        self.add_widget(TextInput(password=True, multiline=False))

class MyApp(App):

    def build(self):
        # 将构建的自定义GridLayout实例化返回
        return MyGridLayout()

if __name__ == '__main__':

界面样式: 1.png

构建网格布局,必须指定cols或者rows, 否则会抛出异常: [WARNING] <__main__.MyGridLayout object at 0x106a873f0> have no cols or rows set, layout is not triggered.

GridLayout的官方文档

API说明

kivy.uix.gridlayout.GridLayout

属性和方法

rows

网格的行数量

类型: NumericProperty

默认值:None

cols

网格的列数量

类型: NumericProperty

默认值:None

col_default_width

列使用的默认最小尺寸

类型:NumericProperty

默认值:0

row_default_height

行使用的默认最小尺寸

类型:NumericProperty

默认值:0

col_force_default

强制列使用默认值, 如果设置为True, 则忽略widthsize_hint_x设置的值.

类型:BooleanProperty

默认值:False

row_force_default

强制行使用默认值, 如果设置为True, 则忽略heightsize_hint_y设置的值.

类型:BooleanProperty

默认值:False

rows_minimum

行最小的高度

类型:DictProperty

默认值:{}

cols_minimum

列最小的宽度

类型:DictProperty

默认值:{}

do_layout(*largs)

当布局由触发器调用时,会调用此函数

minimum_size

自动计算容纳所有子项所需的最小尺寸

类型: ReferenceListProperty,由(minimum_width, minimum_height)构成, 只读

minimum_width

自动计算容纳所有子项所需的最小宽度

类型: NumericProperty

默认值:0

minimum_heigh

自动计算容纳所有子项所需的最小高度

类型: NumericProperty

默认值:0

orientation

布局的方向, 即每一个Widget都是以什么样的顺序被填充进布局的.

有效的布局有:lr-tbtb-lrrl-tbtb-rllr-btbt-lrrl-btbt-rl 字母的意义分别是

lr: 从左向右

tb: 从上向下

字母颠倒则方向相反

类型:OptionProperty

默认值:lr-tb

padding

布局框与每个内部Widget的填充间距.

有3种使用方法,分别是:

  • [padding_left, padding_top, padding_right, padding_bottom]
  • [padding_horizontal, padding_vertical]
  • [padding]

类型:VariableListProperty

默认值:[0, 0, 0, 0]

spacing

子组件的间距

有2种使用方法,分别是:

  • [spacing_horizontal, spacing_vertical]
  • [spacing]

类型:VariableListProperty

默认值:[0, 0]

案例

import os

from kivy.app import App
from kivy.graphics import Color, Rectangle
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.core.text import LabelBase

# 可以在这里下载喜欢的字体: https://font.chinaz.com/
font_path = os.path.join(os.path.dirname(__file__), 'youaimoshouheiti-regular.ttf')
##覆盖默认的字体Roboto
LabelBase.register('Roboto', font_path)
LabelBase.register('自定义字体', font_path)


class StyledGridLayout(GridLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # 此段代码是为了给GridLayout设置一个背景色,在布局更新时, 背景色随之更新
        with self.canvas.before:
            Color(0.5, 0.5, 1)  # 设置背景色为浅蓝色,RGB值 (0.5, 0.5, 1)
            self.rect = Rectangle(size=self.size, pos=self.pos)
        self.bind(size=self.update_rect, pos=self.update_rect)

        self.cols = 2
        self.rows = 2

        #设置内部组件间的距离
        # 统一40
        self.spacing = 40
        # 左右30, 上下50
        self.spacing = [30, 50]

        # 设置布局的边框距离内部组件的距离
        # 统一100
        self.padding = 100
        # 左右100, 上下150
        self.padding = [100, 150]
        # 左20, 上40, 右60, 下80
        self.padding = [20, 40, 60, 80]

        # 强制设置宽度为300
        self.col_default_width = 300
        # self.col_force_default = True

        # 强制设置高度为100
        self.row_default_height = 100
        self.row_force_default = True

        self.add_widget(Button(text="壹"))
        self.add_widget(Button(text="贰"))
        self.add_widget(Button(text="叁"))
        self.add_widget(Button(text="肆"))

    def update_rect(self, *args):
        self.rect.size = self.size
        self.rect.pos = self.pos


class GridLayoutApp(App):

    def build(self):
        return StyledGridLayout()


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

效果图

11.png

2.PageLayout

PageLayout用于创建一个简单的可以翻页的布局. PageLayout并不遵循这些属性: size_hint, size_hint_min, size_hit_max, pos_hint

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.pagelayout import PageLayout


class MyPageLayout(PageLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        self.button = Button(text='Button1', background_color = 'red')
        self.add_widget(self.button)

        self.swipe_threshold = .1

        gl2 = GridLayout()
        gl2.cols = 1
        gl2.rows = 2

        gl2.add_widget(Button(text='Button2', background_color = 'blue'))
        gl2.add_widget(Button(text='Button3', background_color = 'yellow'))

        self.add_widget(gl2)


class PageLayoutApp(App):

    def build(self):
        return MyPageLayout()


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

界面样式

image.png

image.png

此例中, PageLayout

  • 首先,添加了一个Button作为第一页的内容(第一张图片);
  • 其次,添加了一个GridLayout作为第二页的内容(第二张图片)

一般情况下, PageLayout是作为其它Layout的容器存在的,因此放置诸如GridLayout的布局等是推荐的方式.

PageLayout的官方文档

API说明

kivy.uix.pagelayout.PageLayout

属性和方法

anim_kwargs

用于构建动画的关键参数

类型:DictProperty

默认值:{‘d’: .5, ‘t’: ‘in_quad’}

border

当前页面边框的宽度,用于在需要时显示上一页/下一页滑动区域

类型:NumericProperty

默认值:50dp

do_layout(*largs)

当布局由触发器调用时,会调用此函数. 如果自定义PageLayout的子类, 不应该覆盖此方法, 而是应该重写_trigger_layout()

类型:function

on_touch_down(touch)

当触摸按下时, 所采取的动作

类型:function

参数: kivy.input.motionevent.MotionEvent

返回值: bool, 如果为真,则触摸事件的分发将停止; 如果为假,事件将继续分发给Widget树的其余部分

on_touch_move(touch)

当触摸移动时, 所采取的动作

类型:function

参数: kivy.input.motionevent.MotionEvent

返回值: bool, 如果为真,则触摸事件的分发将停止; 如果为假,事件将继续分发给Widget树的其余部分

on_touch_up(touch)

当触摸松开时, 所采取的动作

类型:function

参数: kivy.input.motionevent.MotionEvent

返回值: bool, 如果为真,则触摸事件的分发将停止; 如果为假,事件将继续分发给Widget树的其余部分

page

想要展示的页面的索引

类型:NumericProperty

默认值:0

swipe_threshold

用于触发滑动操作的阈值,以组件大小的比例表示. 比如设置为.5, 就需要滑动下一页到整个PageLayout的50%才会触发翻页.

类型:NumericProperty

默认值:.5

3.BoxLayout

BoxLayout把其内部的Widget按照横向或者纵向的顺序依次排列.

可以通过设置子Widget的size_hint属性, 来改变子组件在BoxLayout的空间占比.

box_layout.add_widget(Button(text='Hello', size_hint=(.7, 1)))会添加一个横向占比70%的按钮.

控制位置的属性在此组件中只能部分被使用, 这依赖于属性orientation: 如果orientation = 'vertical': x, right, center_x是生效的; 如果orientation = 'horizontal': y, top, center_y是生效的.

固定尺寸的子组件优先占用空间, 其它按"比例占用的子组件"再依据自身空间占比在剩余空间中计算尺寸.

如:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button


class MyBoxLayout(BoxLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # self.size = (1000, 500)

        # 可以设置布局是横向or纵向
        self.orientation = 'horizontal'
        # self.orientation = 'vertical'

        self.add_widget(Button(text='button1', background_color='red', size= (150, 500), size_hint= (None, None)))
        self.add_widget(Button(text='button2', background_color='blue', size_hint=(.5, 1)))
        self.add_widget(Button(text='button3', background_color='green', size_hint=(.5, 1)))


class MyBoxLayoutApp(App):

    def build(self):
        return MyBoxLayout()


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

上例中优先计算button1的尺寸, 其次button2和button3再根据比例计算尺寸, 展示效果:

image.png

BoxLayout的官方文档

API说明

kivy.uix.boxlayout.BoxLayout

属性和方法

add_widget(widget, *args, **kwargs)

向BoxLayout中添加子Widget, 其它Layout也有同样的方法

入参:

  • widget:子组件;
  • index:添加是下标位置, 默认一个一个往后加;
  • canvas:str, widget的画布效果, 可以是before, after, None
do_layout(**largs)

当布局由触发器调用时,会调用此函数. 如果自定义PageLayout的子类, 不应该覆盖此方法, 而是应该重写_trigger_layout()

类型:function

minimum_height

自动计算出的容纳所有子元素所需的最小高度

类型:NumericProperty

默认值:0, 这是一个只读属性

minimum_width

自动计算出的容纳所有子元素所需的最小宽度

类型:NumericProperty

默认值:0, 这是一个只读属性

minimum_size

自动计算出的容纳所有子元素所需的最小尺寸

类型:ReferenceListProperty

默认值:(0,0), 这是一个只读属性

orientation

布局的方向

类型:OptionProperty

默认值:horizontal, 可选值:verticalhorizontal.

padding

布局框与每个内部Widget的填充间距.

有3种使用方法,分别是:

  • [padding_left, padding_top, padding_right, padding_bottom]
  • [padding_horizontal, padding_vertical]
  • [padding]

类型:VariableListProperty

默认值:[0, 0, 0, 0]

spacing

子组件的间距

有2种使用方法,分别是:

  • [spacing_horizontal, spacing_vertical]
  • [spacing]

类型:VariableListProperty

默认值:[0, 0]

4.StackLayout

StackLayout可以以水平或者垂直的方式, 尽可能多的填充子Widget. 每个子Widget的尺寸都可以不相同.

比如我们可以使用按钮写一面有趣的墙:

image.png

它是用StackLayout实现的:

from kivy.app import App
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.stacklayout import StackLayout


class MyStackLayout(StackLayout):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        
        for i in range(10):
            if i % 2 == 0:
                # 某些行是有半截砖的, 2个半截构成比整砖的构成, 会多1块
                bricks_each_line = 11
            else:
                # 如果当前行全部是整转, 就需要10块
                bricks_each_line = 10

            for j in range(bricks_each_line):
                if (j == 0 or j == 10) and i % 2 == 0:
                    # 间隔的某些行左右会多出2块半截的砖(stackLayout中的Widget的大小可以不一致)
                    x_hint = .05
                else:
                    # 定义每块砖的横向占比是0.1(10%), 10块砖刚好塞满100%
                    x_hint = .1

                # 每块转按照计算的大小比例排放, 由于是放10层, 因此每层需要占0.1(即10%)
                self.add_widget(Button(text='', size_hint=(x_hint, .1)))


class StackLayoutApp(App):
    def build(self):
        # 设置墙的宽度和高度
        Window.size = (1000, 600)
        return MyStackLayout()


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

通过子组件的size_hint属性, 可以设置其在StackLayout中的尺寸占比.

另外,可以在App的build()方法中设置Window.size = (x, y)来完成整个窗口的大小的调整.

StackLayout的官方文档

API说明

kivy.uix.stacklayout.StackLayout

属性和方法

do_layout(*largs)

当布局由触发器调用时,会调用此函数. 如果自定义PageLayout的子类, 不应该覆盖此方法, 而是应该重写_trigger_layout()

类型:function

minimum_width

自动计算容纳所有子项所需的最小宽度, 它由布局自动设置, 无序调整

类型: NumericProperty

默认值:0

minimum_heigh

自动计算容纳所有子项所需的最小高度, 它由布局自动设置, 无序调整

类型: NumericProperty

默认值:0

minimum_size

自动计算出的容纳所有子元素所需的最小尺寸, 它由布局自动设置, 无序调整

类型:ReferenceListProperty

默认值:(0,0), 这是一个只读属性

orientation

布局的方向, 即每一个Widget都是以什么样的顺序被填充进布局的.

有效的布局有:lr-tbtb-lrrl-tbtb-rllr-btbt-lrrl-btbt-rl 字母的意义分别是

lr: 从左向右

tb: 从上向下

字母颠倒则方向相反

类型:OptionProperty

默认值:lr-tb

padding

布局框与每个内部Widget的填充间距.

有3种使用方法,分别是:

  • [padding_left, padding_top, padding_right, padding_bottom]
  • [padding_horizontal, padding_vertical]
  • [padding]

类型:VariableListProperty

默认值:[0, 0, 0, 0]

spacing

子组件的间距

有2种使用方法,分别是:

  • [spacing_horizontal, spacing_vertical]
  • [spacing]