AWidget
是 Kivy 中 GUI 界面的基本构建块。它提供了一个Canvas
可用于在屏幕上绘制的。它接收事件并对它们做出反应。有关该类的深入解释Widget
,请查看模块文档。
Kivy 中的小部件以树的形式组织。您的应用程序有一个根小部件 ,它通常children
可以有 children
自己的。小部件的子项表示为children
属性 Kivy ListProperty
。
可以使用以下方法操作小部件树:
例如,如果你想在 BoxLayout 中添加一个按钮,你可以这样做:
Copy layout = BoxLayout(padding=10)
button = Button(text='My first button')
layout.add_widget(button)
按钮添加到布局:按钮的父属性将设置为布局;布局会将按钮添加到其子列表中。从布局中删除按钮:
Copy layout.remove_widget(button)
删除后,按钮的父属性将设置为 None,并且布局将从其子列表中删除按钮。
如果要清除小部件内的所有子项,请使用 clear_widgets()
方法:
Copy layout.clear_widgets()
警告
永远不要自己操纵子列表,除非您真的知道自己在做什么。小部件树与图形树相关联。例如,如果您将一个小部件添加到子列表中而没有将其画布添加到图形树中,那么该小部件将是一个子对象,是的,但不会在屏幕上绘制任何内容。此外,您可能在进一步调用 add_widget、remove_widget 和 clear_widgets 时遇到问题。
Widget 类实例的children
列表属性包含所有子项。您可以通过执行以下操作轻松遍历树:
Copy root = BoxLayout()
# ... add widgets to root ...
for child in root.children:
print(child)
但是,必须谨慎使用。如果您打算使用上一节中显示的方法之一修改子列表,则必须像这样使用列表的副本:
Copy 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
, 是绝对大小。
小部件绘制的顺序基于小部件在小部件树中的位置。该add_widget
方法采用一个索引 参数,可用于指定其在小部件树中的位置:
Copy root.add_widget(widget, index)
索引较低的小部件将绘制在索引较高的小部件上方。请记住,索引 的默认值为 0,因此除非另有说明,否则稍后添加的小部件将绘制在其他小部件之上。
layout
是一种特殊的小部件,可以控制其子部件的大小和位置。有不同种类的布局,允许对它们的孩子进行不同的自动组织。布局使用size_hint
和pos_hint
属性来确定它们的size
和。pos
children
BoxLayout :以相邻方式(垂直或水平)排列小部件,以填充所有空间。子项的 size_hint 属性可用于更改每个子项允许的比例,或为其中一些设置固定大小。
GridLayout :在网格中排列小部件。您必须至少指定网格的一维,以便 kivy 可以计算元素的大小以及如何排列它们。
StackLayout :将小部件彼此相邻排列,但在其中一个维度中设置大小,而不是试图使它们适合整个空间。这对于显示具有相同预定义大小的子项很有用。
AnchorLayout :一个简单的布局,只关心孩子的位置。它允许将孩子放在相对于布局边界的位置。 size_hint 不被尊重。
FloatLayout :允许放置具有任意位置和大小的子项,绝对或相对于布局大小。默认 size_hint (1, 1) 将使每个子元素与整个布局的大小相同,因此如果您有多个子元素,您可能想要更改此值。您可以将 size_hint 设置为 (None, None) 以使用 size 的绝对大小 。这个小部件也支持pos_hint ,它是一个相对于布局位置的字典设置位置。
RelativeLayout :行为就像 FloatLayout,除了子位置是相对于布局位置,而不是屏幕。
检查各个布局的文档以获得更深入的理解。
size_hint
和pos_hint
:
size_hint
是一个ReferenceListProperty
和 size_hint_x
。size_hint_y
它接受从0 到1 或None 的值 ,默认为(1, 1) 。这表示如果小部件在布局中,则布局将在两个方向(相对于布局大小)为其分配尽可能多的位置。
例如,设置size_hint
为 (0.5, 0.8) 将使小部件成为 a 内部可用大小的 50% 宽度和 80%Widget
高度layout
。
考虑以下示例:
Copy 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__)
):
Copy cd $KIVYDIR/examples/demo/kivycatalog
python main.py
将出现一个新窗口。单击左侧“欢迎”下方的区域Spinner
,并将那里的文本替换为上面的 kv 代码。
从上图中可以看出,Button 占据了 100% 的布局 size
。
size_hint_x
将/更改size_hint_y
为 .5 将占/Widget
的 50% 。layout
width
height
你可以在这里看到,虽然我们指定size_hint_x
和size_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
将指定size
从boxlayout
. 在我们的示例中,第一个Button
为 指定 .5 size_hint_x
。小部件的空间计算如下:
Copy 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 的其余部分width
在children
. 在我们的例子中,这意味着第二个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
不同的方式,但通常您可以将值添加到任何属性pos
(x
, y
, right
, top
, center_x
, center_y
)以使其 Widget
相对于其定位parent
。
我们在kivycatalog中用如下代码进行实验,pos_hint
直观地理解一下:
Copy 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
了解它对小部件位置的影响。
关于布局的常见问题之一是:
Copy "How to add a background image/color/video/... to a Layout"
布局本质上没有视觉表示:默认情况下它们没有画布说明。但是,您可以轻松地将画布指令添加到布局实例,就像添加彩色背景一样:
在 Python 中:
Copy 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)
不幸的是,这只会在布局的初始位置和大小处绘制一个矩形。为了确保矩形绘制在布局内部,当布局尺寸/位置发生变化时,我们需要监听任何变化并更新矩形尺寸和位置。我们可以这样做:
Copy 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)
千伏:
Copy 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
和 的值将在的 更改 时更新。size
pos
floatlayout
现在我们将上面的代码片段放入 Kivy App 的 shell 中。
纯 Python 方式:
Copy 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 语言:
Copy 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:
Copy 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 语言:
Copy 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:
Copy <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 应用程序时:
Copy 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/... 以使用特定的纹理:
Copy Rectangle:
texture: reference to a texture
我们用它来显示动画背景:
Copy 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 行开始:
Copy texture: self.background_image.texture
这指定BorderImage 的纹理属性将在background_image 的纹理 属性更新时更新。我们在第 40 行定义了 background_image 属性:
Copy background_image = ObjectProperty(...
这将background_image 设置为ObjectProperty
我们在其中添加一个Image
小部件。图像小部件具有纹理 属性;在你看到 self.background_image.texture 的 地方,它设置了一个参考,texture ,到这个属性。widgetImage
支持动画:每当动画变化时更新图像的纹理,BorderImage 指令的纹理在此过程中更新。
您也可以将自定义数据 blit 到纹理。有关详细信息,请查看 的文档Texture
。
是的!看到该过程的可扩展性非常有趣。
Kivy默认的长度单位是像素,所有的尺寸和位置都默认用它来表示。您可以用其他单位表示它们,这有助于在设备之间实现更好的一致性(它们会自动转换为以像素为单位的大小)。
可用单位为pt
、mm
、cm
、inch
和dp
。sp
您可以在文档中了解它们的用法metrics
。
您还可以尝试使用它screen
来为您的应用程序模拟各种设备屏幕。
如果您的应用程序由多个屏幕组成,您可能需要一种从一个屏幕导航Screen
到另一个屏幕的简单方法。幸运的是,有一个 ScreenManager
类允许您单独定义屏幕,并将屏幕设置TransitionBase
为另一个。