小部件

小部件简介

AWidget是 Kivy 中 GUI 界面的基本构建块。它提供了一个Canvas可用于在屏幕上绘制的。它接收事件并对它们做出反应。有关该类的深入解释Widget,请查看模块文档。

操作小部件树

Kivy 中的小部件以树的形式组织。您的应用程序有一个根小部件,它通常children可以有 children自己的。小部件的子项表示为children 属性 Kivy ListProperty

可以使用以下方法操作小部件树:

例如,如果你想在 BoxLayout 中添加一个按钮,你可以这样做:

layout = BoxLayout(padding=10)
button = Button(text='My first button')
layout.add_widget(button)

按钮添加到布局:按钮的父属性将设置为布局;布局会将按钮添加到其子列表中。从布局中删除按钮:

layout.remove_widget(button)

删除后,按钮的父属性将设置为 None,并且布局将从其子列表中删除按钮。

如果要清除小部件内的所有子项,请使用 clear_widgets()方法:

layout.clear_widgets()

警告

永远不要自己操纵子列表,除非您真的知道自己在做什么。小部件树与图形树相关联。例如,如果您将一个小部件添加到子列表中而没有将其画布添加到图形树中,那么该小部件将是一个子对象,是的,但不会在屏幕上绘制任何内容。此外,您可能在进一步调用 add_widget、remove_widget 和 clear_widgets 时遇到问题。

遍历树

Widget 类实例的children列表属性包含所有子项。您可以通过执行以下操作轻松遍历树:

root = BoxLayout()
# ... add widgets to root ...
for child in root.children:
    print(child)

但是,必须谨慎使用。如果您打算使用上一节中显示的方法之一修改子列表,则必须像这样使用列表的副本:

for child in root.children[:]:
    # manipulate the tree. For example here, remove all widgets that have a
    # width < 100
    if child.width < 100:
        root.remove_widget(child)

默认情况下,小部件不会影响其子项的大小/位置。该 pos属性是屏幕坐标中的绝对位置(除非您使用relativelayout. 稍后会详细介绍)并且size, 是绝对大小。

小部件 Z 索引

小部件绘制的顺序基于小部件在小部件树中的位置。该add_widget 方法采用一个索引参数,可用于指定其在小部件树中的位置:

root.add_widget(widget, index)

索引较低的小部件将绘制在索引较高的小部件上方。请记住,索引的默认值为 0,因此除非另有说明,否则稍后添加的小部件将绘制在其他小部件之上。

用布局组织

layout是一种特殊的小部件,可以控制其子部件的大小和位置。有不同种类的布局,允许对它们的孩子进行不同的自动组织。布局使用size_hintpos_hint 属性来确定它们的size和。poschildren

BoxLayout:以相邻方式(垂直或水平)排列小部件,以填充所有空间。子项的 size_hint 属性可用于更改每个子项允许的比例,或为其中一些设置固定大小。

GridLayout:在网格中排列小部件。您必须至少指定网格的一维,以便 kivy 可以计算元素的大小以及如何排列它们。

StackLayout:将小部件彼此相邻排列,但在其中一个维度中设置大小,而不是试图使它们适合整个空间。这对于显示具有相同预定义大小的子项很有用。

AnchorLayout:一个简单的布局,只关心孩子的位置。它允许将孩子放在相对于布局边界的位置。 size_hint不被尊重。

FloatLayout:允许放置具有任意位置和大小的子项,绝对或相对于布局大小。默认 size_hint (1, 1) 将使每个子元素与整个布局的大小相同,因此如果您有多个子元素,您可能想要更改此值。您可以将 size_hint 设置为 (None, None) 以使用 size 的绝对大小。这个小部件也支持pos_hint,它是一个相对于布局位置的字典设置位置。

RelativeLayout:行为就像 FloatLayout,除了子位置是相对于布局位置,而不是屏幕。

检查各个布局的文档以获得更深入的理解。

size_hintpos_hint

size_hint是一个ReferenceListPropertysize_hint_xsize_hint_y它接受从01None的值 ,默认为(1, 1)。这表示如果小部件在布局中,则布局将在两个方向(相对于布局大小)为其分配尽可能多的位置。

例如,设置size_hint为 (0.5, 0.8) 将使小部件成为 a 内部可用大小的 50% 宽度和 80%Widget高度layout

考虑以下示例:

BoxLayout:
    Button:
        text: 'Button 1'
        # default size_hint is 1, 1, we don't need to specify it explicitly
        # however it's provided here to make things clear
        size_hint: 1, 1

现在通过键入以下内容加载 kivy 目录,但将 $KIVYDIR 替换为您的安装目录(可通过 发现 os.path.dirname(kivy.__file__)):

cd $KIVYDIR/examples/demo/kivycatalog
python main.py

将出现一个新窗口。单击左侧“欢迎”下方的区域Spinner,并将那里的文本替换为上面的 kv 代码。

从上图中可以看出,Button占据了 100% 的布局 size

size_hint_x将/更改size_hint_y为 .5 将占/Widget的 50% 。layout widthheight

你可以在这里看到,虽然我们指定size_hint_xsize_hint_y都为 .5,但size_hint_y似乎只是兑现。那是因为boxlayout 控制什么size_hint_y时候orientation垂直的size_hint_x 什么时候orientation是“水平的”。受控维度的大小是根据总数计算的。children中的boxlayout。在这个例子中,一个孩子 size_hint_y控制了 (.5/.5 = 1)。因此,小部件占据了父布局高度的 100%。

让我们Button向 中添加另一个layout,看看会发生什么。

boxlayout就其本质而言,将可用空间 children平均分配。在我们的示例中,比例是 50-50,因为我们有两个 children. 让我们对其中一个孩子使用 size_hint 并查看结果。

如果一个孩子指定size_hint,这Widget 将指定sizeboxlayout. 在我们的示例中,第一个Button为 指定 .5 size_hint_x。小部件的空间计算如下:

first child's size_hint divided by
first child's size_hint + second child's size_hint + ...n(no of children)

.5/(.5+1) = .333...

BoxLayout 的其余部分widthchildren. 在我们的例子中,这意味着第二个Button占据了 66.66% 的.layout width

尝试size_hint以适应它。

size如果你想控制a 的绝对值Widget,你可以将 size_hint_x/size_hint_y或两者都设置为None,这样小部件的width和或 height属性将得到尊重。

pos_hint是一个字典,默认为空。至于size_hint,布局遵循 pos_hint不同的方式,但通常您可以将值添加到任何属性posx, y, right, top, center_x, center_y)以使其 Widget相对于其定位parent

我们在kivycatalog中用如下代码进行实验,pos_hint 直观地理解一下:

FloatLayout:
    Button:
        text: "We Will"
        pos: 100, 100
        size_hint: .2, .4
    Button:
        text: "Wee Wiill"
        pos: 200, 200
        size_hint: .4, .2

    Button:
        text: "ROCK YOU!!"
        pos_hint: {'x': .3, 'y': .6}
        size_hint: .5, .2

这给了我们:

与 一样size_hint,您应该尝试pos_hint了解它对小部件位置的影响。

为布局添加背景

关于布局的常见问题之一是:

"How to add a background image/color/video/... to a Layout"

布局本质上没有视觉表示:默认情况下它们没有画布说明。但是,您可以轻松地将画布指令添加到布局实例,就像添加彩色背景一样:

在 Python 中:

from kivy.graphics import Color, Rectangle

with layout_instance.canvas.before:
    Color(0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
    self.rect = Rectangle(size=layout_instance.size,
                           pos=layout_instance.pos)

不幸的是,这只会在布局的初始位置和大小处绘制一个矩形。为了确保矩形绘制在布局内部,当布局尺寸/位置发生变化时,我们需要监听任何变化并更新矩形尺寸和位置。我们可以这样做:

with layout_instance.canvas.before:
    Color(0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
    self.rect = Rectangle(size=layout_instance.size,
                           pos=layout_instance.pos)

def update_rect(instance, value):
    instance.rect.pos = instance.pos
    instance.rect.size = instance.size

# listen to size and position changes
layout_instance.bind(pos=update_rect, size=update_rect)

千伏:

FloatLayout:
    canvas.before:
        Color:
            rgba: 0, 1, 0, 1
        Rectangle:
            # self here refers to the widget i.e FloatLayout
            pos: self.pos
            size: self.size

kv 声明设置了隐式绑定:最后两行 kv 确保矩形的pos和 的值将在的 更改 时更新。sizeposfloatlayout

现在我们将上面的代码片段放入 Kivy App 的 shell 中。

纯 Python 方式:

from kivy.app import App
from kivy.graphics import Color, Rectangle
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button


class RootWidget(FloatLayout):

    def __init__(self, **kwargs):
        # make sure we aren't overriding any important functionality
        super(RootWidget, self).__init__(**kwargs)

        # let's add a Widget to this layout
        self.add_widget(
            Button(
                text="Hello World",
                size_hint=(.5, .5),
                pos_hint={'center_x': .5, 'center_y': .5}))


class MainApp(App):

    def build(self):
        self.root = root = RootWidget()
        root.bind(size=self._update_rect, pos=self._update_rect)

        with root.canvas.before:
            Color(0, 1, 0, 1)  # green; colors range from 0-1 not 0-255
            self.rect = Rectangle(size=root.size, pos=root.pos)
        return root

    def _update_rect(self, instance, value):
        self.rect.pos = instance.pos
        self.rect.size = instance.size

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

使用 kv 语言:

from kivy.app import App
from kivy.lang import Builder


root = Builder.load_string('''
FloatLayout:
    canvas.before:
        Color:
            rgba: 0, 1, 0, 1
        Rectangle:
            # self here refers to the widget i.e FloatLayout
            pos: self.pos
            size: self.size
    Button:
        text: 'Hello World!!'
        size_hint: .5, .5
        pos_hint: {'center_x':.5, 'center_y': .5}
''')

class MainApp(App):

    def build(self):
        return root

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

这两个应用程序应该看起来像这样:

为自定义布局规则/类的背景添加颜色

如果我们需要使用多个布局,我们将背景添加到布局实例的方式很快就会变得很麻烦。为了解决这个问题,您可以子类化 Layout 并创建您自己的添加背景的布局。

使用 Python:

from kivy.app import App
from kivy.graphics import Color, Rectangle
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import AsyncImage


class RootWidget(BoxLayout):
    pass


class CustomLayout(FloatLayout):

    def __init__(self, **kwargs):
        # make sure we aren't overriding any important functionality
        super(CustomLayout, self).__init__(**kwargs)

        with self.canvas.before:
            Color(0, 1, 0, 1)  # green; colors range from 0-1 instead of 0-255
            self.rect = Rectangle(size=self.size, pos=self.pos)

        self.bind(size=self._update_rect, pos=self._update_rect)

    def _update_rect(self, instance, value):
        self.rect.pos = instance.pos
        self.rect.size = instance.size


class MainApp(App):

    def build(self):
        root = RootWidget()
        c = CustomLayout()
        root.add_widget(c)
        c.add_widget(
            AsyncImage(
                source="http://www.everythingzoomer.com/wp-content/uploads/2013/01/Monday-joke-289x277.jpg",
                size_hint= (1, .5),
                pos_hint={'center_x':.5, 'center_y':.5}))
        root.add_widget(AsyncImage(source='http://www.stuffistumbledupon.com/wp-content/uploads/2012/05/Have-you-seen-this-dog-because-its-awesome-meme-puppy-doggy.jpg'))
        c = CustomLayout()
        c.add_widget(
            AsyncImage(
                source="http://www.stuffistumbledupon.com/wp-content/uploads/2012/04/Get-a-Girlfriend-Meme-empty-wallet.jpg",
                size_hint= (1, .5),
                pos_hint={'center_x':.5, 'center_y':.5}))
        root.add_widget(c)
        return root

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

使用 kv 语言:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder


Builder.load_string('''
<CustomLayout>
    canvas.before:
        Color:
            rgba: 0, 1, 0, 1
        Rectangle:
            pos: self.pos
            size: self.size

<RootWidget>
    CustomLayout:
        AsyncImage:
            source: 'http://www.everythingzoomer.com/wp-content/uploads/2013/01/Monday-joke-289x277.jpg'
            size_hint: 1, .5
            pos_hint: {'center_x':.5, 'center_y': .5}
    AsyncImage:
        source: 'http://www.stuffistumbledupon.com/wp-content/uploads/2012/05/Have-you-seen-this-dog-because-its-awesome-meme-puppy-doggy.jpg'
    CustomLayout
        AsyncImage:
            source: 'http://www.stuffistumbledupon.com/wp-content/uploads/2012/04/Get-a-Girlfriend-Meme-empty-wallet.jpg'
            size_hint: 1, .5
            pos_hint: {'center_x':.5, 'center_y': .5}
''')

class RootWidget(BoxLayout):
    pass

class CustomLayout(FloatLayout):
    pass

class MainApp(App):

    def build(self):
        return RootWidget()

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

这两个应用程序应该看起来像这样:

在自定义布局类中定义背景,确保它将在 CustomLayout 的每个实例中使用。

现在,要在全局的内置 Kivy 布局的背景中添加图像或颜色 ,我们需要覆盖相关布局的 kv 规则。考虑 GridLayout:

<GridLayout>
    canvas.before:
        Color:
            rgba: 0, 1, 0, 1
        BorderImage:
            source: '../examples/widgets/sequenced_images/data/images/button_white.png'
            pos: self.pos
            size: self.size

然后,当我们将这段代码放入 Kivy 应用程序时:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.lang import Builder


Builder.load_string('''
<GridLayout>
    canvas.before:
        BorderImage:
            # BorderImage behaves like the CSS BorderImage
            border: 10, 10, 10, 10
            source: '../examples/widgets/sequenced_images/data/images/button_white.png'
            pos: self.pos
            size: self.size

<RootWidget>
    GridLayout:
        size_hint: .9, .9
        pos_hint: {'center_x': .5, 'center_y': .5}
        rows:1
        Label:
            text: "I don't suffer from insanity, I enjoy every minute of it"
            text_size: self.width-20, self.height-20
            valign: 'top'
        Label:
            text: "When I was born I was so surprised; I didn't speak for a year and a half."
            text_size: self.width-20, self.height-20
            valign: 'middle'
            halign: 'center'
        Label:
            text: "A consultant is someone who takes a subject you understand and makes it sound confusing"
            text_size: self.width-20, self.height-20
            valign: 'bottom'
            halign: 'justify'
''')

class RootWidget(FloatLayout):
    pass


class MainApp(App):

    def build(self):
        return RootWidget()

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

结果应该是这个样子:

当我们覆盖类 GridLayout 的规则时,在我们的应用程序中对此类的任何使用都将显示该图像。

动画背景怎么样?

您可以设置绘图指令,如 Rectangle/BorderImage/Ellipse/... 以使用特定的纹理:

Rectangle:
    texture: reference to a texture

我们用它来显示动画背景:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.image import Image
from kivy.properties import ObjectProperty
from kivy.lang import Builder


Builder.load_string('''
<CustomLayout>
    canvas.before:
        BorderImage:
            # BorderImage behaves like the CSS BorderImage
            border: 10, 10, 10, 10
            texture: self.background_image.texture
            pos: self.pos
            size: self.size

<RootWidget>
    CustomLayout:
        size_hint: .9, .9
        pos_hint: {'center_x': .5, 'center_y': .5}
        rows:1
        Label:
            text: "I don't suffer from insanity, I enjoy every minute of it"
            text_size: self.width-20, self.height-20
            valign: 'top'
        Label:
            text: "When I was born I was so surprised; I didn't speak for a year and a half."
            text_size: self.width-20, self.height-20
            valign: 'middle'
            halign: 'center'
        Label:
            text: "A consultant is someone who takes a subject you understand and makes it sound confusing"
            text_size: self.width-20, self.height-20
            valign: 'bottom'
            halign: 'justify'
''')


class CustomLayout(GridLayout):

    background_image = ObjectProperty(
        Image(
            source='../examples/widgets/sequenced_images/data/images/button_white_animated.zip',
            anim_delay=.1))


class RootWidget(FloatLayout):
    pass


class MainApp(App):

    def build(self):
        return RootWidget()

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

要了解这里发生了什么,请从第 13 行开始:

texture: self.background_image.texture

这指定BorderImage纹理属性将在background_image纹理属性更新时更新。我们在第 40 行定义了 background_image 属性:

background_image = ObjectProperty(...

这将background_image设置为ObjectProperty我们在其中添加一个Image 小部件。图像小部件具有纹理属性;在你看到 self.background_image.texture 的地方,它设置了一个参考,texture,到这个属性。widgetImage支持动画:每当动画变化时更新图像的纹理,BorderImage 指令的纹理在此过程中更新。

您也可以将自定义数据 blit 到纹理。有关详细信息,请查看 的文档Texture

嵌套布局

是的!看到该过程的可扩展性非常有趣。

尺寸和位置指标

Kivy默认的长度单位是像素,所有的尺寸和位置都默认用它来表示。您可以用其他单位表示它们,这有助于在设备之间实现更好的一致性(它们会自动转换为以像素为单位的大小)。

可用单位为ptmmcminchdpsp您可以在文档中了解它们的用法metrics

您还可以尝试使用它screen来为您的应用程序模拟各种设备屏幕。

使用屏幕管理器进行屏幕分离

如果您的应用程序由多个屏幕组成,您可能需要一种从一个屏幕导航Screen到另一个屏幕的简单方法。幸运的是,有一个 ScreenManager类允许您单独定义屏幕,并将屏幕设置TransitionBase为另一个。

Last updated