编程向导4.7部件
一、部件介绍
在Kivy中,部件是创建GUI接口的基本。它提供了一个画板,能用来在屏幕上绘画。它能接收事件并响应它们。有关Widget类的深度的解释,请参看模块文档。
二、操纵部件树
在Kivy中部件使用树来管理。你的应用程序有一个根部件,它通常有拥有自己子部件的子部件。子部件被children(一个Kivy的列表属性(ListProperty)))特征值代表.
部件树能使用以下方法进行操作:
- add_widget():添加一个部件作为子部件
- remove_widget():从子部件列表中移除一个部件
- clear_widgets():移除所有的子部件
例如如果你想在一个盒子布局(BoxLayout)中添加一个按钮,你可以:
layout = BoxLayout(padding = 10)
button = Button(text = 'My First Button')
layout.add_widget(button)
按钮被添加到布局:按钮的父属性被设置为layout,layout将会把button添加到它的子部件列表中。
如果要从layout中移除button,可以:
layout.remove_widget(button)
移除后,按钮的父属性被设置为None,layout将从子部件列表中移除button.如果你想将layout中的所有子部件全部移除,可以:
layout.clear_widgets()
注意:永远不要手动配置子部件列表,除非你真的明白你在做什么。部件树和一个图形树相关联。例如,如果你添加一个部件到子部件列表,但没有添加它的画布到图形树,那么部件将称为一个子部件,但是屏幕上没有任何东西显示。更重要的是,你可能在以后调用add_widget, remove_widget, clear_widgets中会出现问题。
三、遍历部件树
部件类实例的children中包含所有的子部件,你能容易的遍历部件树:
root = BoxLayout()
#...添加子部件到root...
for child in root.children
print(child)
但是,这必须要小心使用。如果你试图使用前面章节提供的方法来修改children,你必须用children列表的拷贝:
for child in root.children[:]:
#配置部件树,例如移除所有width<100的部件
if child.width < 100:
root.remove_widget(child)
默认情况下,部件不影响子部件的尺寸和位置。pos特征值是屏幕坐标的绝对位置。(除非你使用了相对布局(relativelayout)和尺寸)。
四、部件Z索引
渲染部件的顺序是基于在部件树的位置。最后的部件的画布最后被渲染。add_widget有一个index参数,用来设置Z索引:
root.add_widget(widget, index)
五、使用布局管理
布局是一种特殊的部件,它控制它的子部件的尺寸和位置。有不同类型的布局,按照不同的规则自动组织它们的子部件。布局使用size_hint和pos_hint属性来确定它们子部件的尺寸和位置。
(一)盒子布局(BoxLayout)
盒子布局以相邻的方式(或平行或垂直)来安排他们的子部件,填满所有的空间。子部件的size_hint属性能被用来改变被允许的比例或设置固定的尺寸。
(二)网格布局(GridLayout)
网格布局排列部件在网格内。你必须至少制定网格的一个维度,这样Kivy才能计算元素的尺寸及如何排列它们。
(三)堆叠布局(StackLayout)
堆叠布局一个接一个的排列部件,但是在一个维度上使用了一组尺寸,不要试图使它们填满整个空间。它常用来显示有同样尺寸的子部件。
(四)锚点布局(AnchorLayout)
一种简单的布局,它仅关注子部件的位置。它允许在一个相对于布局的边的位置放置子部件。size_hint被忽略。
(五)浮动布局(FloatLayout)
浮动布局允许放置任意位置和尺寸的子部件。默认size_hint(1, 1)将会使每一个子部件有同样的尺寸作为整个布局,所以,如果你有多个子部件,你可能想改变这个值。你能设置set_hint到(None, None)来使用绝对的尺寸。同样也可以使用pos_hint设置位置。
(六)相对布局(RelativeLayout)
有点像FloatLayout,除了子部件的位置是相对于布局位置,而不是屏幕位置。
查看每个布局的文档,可以用更深入的了解。
[size_hint]和[pos_hint]:
- [floatlayout]
- [boxlayout]
- [gridlayout]
- [stacklayout]
- [relativelayout]
- [anchorlayout]
size_hint是一个关于size_hint_x和size_hint_y的ReferenceListProperty.它接受从0到1,或None的值,默认为(1,1)。这表示如果部件在一个布局中,布局会相对于布局尺寸,在两个方向上,尽可能为它分配足够的空间。
设置size_hint到(0.5, 0.8),表示在布局中将会使部件有50%的宽和80%的高可用。
考虑以下代码:
BoxLayout:
Button:
text:'Button 1'
#默认size_hint是1, 1,我们不需要明确地指定它
#但是它在这里被设置会更清晰。
size_hint:1, 1
加载kivy目录:
cd $KIVYDIR/examples/demo/kivycatalog
python main.py
使用你的Kivy的安装路径代替$KIVYDIR。点击盒子布局左侧的按钮。粘贴上面的代码到右边,你将会看到,按钮占有布局100%的尺寸。
改变size_hint_x/size_hint_y到0.5将会时部件有布局50%的宽/高。
你能看到,虽然我们指定了size_hint_x和size_hint_y到0.5,但是仅仅size_hint_x生效了。这是因为盒子布局在orientation是垂直时控制着size_hint_y,在orientation是水平是控制着size_hint_x。被控制维度的尺寸的计算依赖子部件的总的数目。在这个例子中,仅有一个子部件,因此,它将持有100%的父部件的高度。
让我们添加另外一个按钮到布局,看看会发生什么。
盒子布局会为它的子部件平分可用的空间。让我们使用size_hint设置一个按钮的尺寸。第一个按钮指定0.5的size_hint_x,第二个按钮的size_hint_x,默认为1,则总的宽度将变成0.5+1=1.5,第一个按钮的宽度就会变为0.5/1.5=0.333...,约为1/3的宽度。剩下的盒子布局的宽度分配给另一个按钮,约为2/3。如果有多个剩余的子部件,他们会进行平分。
如果你想控制部件的绝对大小,你可以设置size_hint_x/size_hint_y,或者二者均为None,这样部件的width和height特征值就会使用。
pos_hint是一个字典,默认为空。正如size_hint, 布局对使用pos_hint分别对待,通常你可以添加任何pos特征值(x, y, left, top, center_x, center_y),让我们实验下面的代码,以了解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来理解它对部件位置的影响。
六、为布局添加一个背景
经常被询问的关于布局的一个问题是:
如何为一个布局添加一个背景图片/颜色/视频/...
布局本质上没有可视的元素:默认情况下,他们没有画布指令。但是你可以添加画布指令到一个布局实例,正如添加一个背景颜色:
from kivy.graphics import Clolr, Rectangle
with layout_instance.canvas.before:
Clolr(0, 1, 0, 1)#绿色;颜色范围从0~1代替0~255
self.rect = Rectangle(size = layout_instance.size, pos = layout_instance.pos)
不幸的是,这仅仅会在布局的初始位置和尺寸画一个矩形。当布局的尺寸和位置改变时,为确保矩形被画在布局内部,我们需要监听矩形尺寸和位置的任何变动和更新:
with layout_instance.canvas.before:
Color(0, 1, 0, 1)
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
#监听尺寸和位置的更新
layout_instance.bind(pos=update_rect, size=update_rect)
在kv中:
FloatLayout:
canvas.before:
Color:
rgba:0, 1, 0, 1
Rectangle:
#这里的self代表部件,例如BoxLayout
pos:self.pos
size: self.size
kv声明设置了一个隐性的绑定:最后两个kv语句确保pos和size值随着FloatLayout的pos的改变而自动更新。
现在,我们添加一些功能:
- 纯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):
super(RootWidget, self).__init__(**kwargs)
#添加一个按钮到布局
self.add_widget(
Button(
text = 'Hello World'
size_hint(.5, .5)
pos_hint = {'center_x': .5, 'center_y':.5}
)
)
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)
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:
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()
运行效果如下:
添加颜色到背景用一个custom layouts rule/class
如果我们需要使用多重布局的话,这种添加背景到布局实例的方法会变得笨重。为了解决这个问题,我们可以创建布局类的子类,并创建你自己的添加了背景的布局:
- 使用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()
结果如下:
在子类中定义背景,确保它被用在每一个定制布局的实例中。
现在,为了添加一个图片或颜色到内置的Kivy布局背景中,总体来说,我们需要为布局问题重载kv规则。考虑网格布局:
<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()
结果如下:
由于我们重载了网格布局的规则,任何应用该类的地方都会显示图片。
一个动画背景如何显示呢?
你可以设置绘画指令,像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更新时都将被更新。我们定义了background_image属性在40行:
background_image = ObjectProperty(...)
这段代码设置background_miage是一个ObjectProperty,在那儿我们添加了一个Image部件。一个Image部件有一个textuer属性,self.background_image.texture设置了一个对于texture的引用。Image部件支持动画:图片的材质在动画改变时会被更新,并且BorderImage指令的材质跟着更新了。
您还可以自定义数据的纹理贴图。更多信息请参阅Texture文档。
七、嵌套布局
当然,关于如何扩展这部分内容应该是很有趣的!
gthank-没有实际内容
八、尺寸和坐标度量
Kivy的默认长度单位是像素(pixel),所有尺寸和位置都使用它。你也可以使用别的单位以获得更好的跨平台的效果。
可用的单位有pt, mm, cm, inch, dp, sp.你可以在metrics文档中了解它们的用法。
你可以用screen应用模拟不同的设备来测试你的应用程序。
九、用屏幕管理进行屏幕分离
如果你的应用程序由不同的屏幕组成,你可能想有一个容易的方式来从一个屏幕导航到另一个屏幕。幸运的是,有一个ScreenManager类,允许你分别定义屏幕,并从一个屏幕到另外一个屏幕设置基本转换(TransitionBase)。