### 编程向导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*属性能被用来改变被允许的比例或设置固定的尺寸。
![box layout](http://ww4.sinaimg.cn/large/577d3ebejw1f12ebmqrovg208w080doa.gif)
##### (二)网格布局(GridLayout)
网格布局排列部件在网格内。你必须至少制定网格的一个维度,这样Kivy才能计算元素的尺寸及如何排列它们。
![grid layout](http://ww4.sinaimg.cn/large/577d3ebejw1f12echrkpmg208g080u0x.gif)
##### (三)堆叠布局(StackLayout)
堆叠布局一个接一个的排列部件,但是在一个维度上使用了一组尺寸,不要试图使它们填满整个空间。它常用来显示有同样尺寸的子部件。
![stack layout](http://ww4.sinaimg.cn/large/577d3ebejw1f12ecrjjieg208g0744kv.gif)
##### (四)锚点布局(AnchorLayout)
一种简单的布局,它仅关注子部件的位置。它允许在一个相对于布局的边的位置放置子部件。*size_hint*被忽略。
![anchor layout](http://ww2.sinaimg.cn/large/577d3ebejw1f12ebyuqfkg209s08wqi1.gif)
##### (五)浮动布局(FloatLayout)
浮动布局允许放置任意位置和尺寸的子部件。默认*size_hint(1, 1)*将会使每一个子部件有同样的尺寸作为整个布局,所以,如果你有多个子部件,你可能想改变这个值。你能设置*set_hint*到(None, None)来使用绝对的尺寸。同样也可以使用*pos_hint*设置位置。
![float layout](http://ww1.sinaimg.cn/large/577d3ebejw1f12ec9b688g20cg074avv.gif)
##### (六)相对布局(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%的尺寸。
![](http://ww4.sinaimg.cn/large/577d3ebejw1f12ee8647oj20m40fx0tw.jpg)
改变size_hint_x/size_hint_y到0.5将会时部件有布局50%的宽/高。
![](http://ww2.sinaimg.cn/large/577d3ebejw1f12eepz4zvj20m40fx75l.jpg)
你能看到,虽然我们指定了size_hint_x和size_hint_y到0.5,但是仅仅size_hint_x生效了。这是因为盒子布局在orientation是垂直时控制着size_hint_y,在orientation是水平是控制着size_hint_x。被控制维度的尺寸的计算依赖子部件的总的数目。在这个例子中,仅有一个子部件,因此,它将持有100%的父部件的高度。
让我们添加另外一个按钮到布局,看看会发生什么。
![](http://ww4.sinaimg.cn/large/577d3ebejw1f12ef9pli9j20m40fxwge.jpg)
盒子布局会为它的子部件平分可用的空间。让我们使用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。如果有多个剩余的子部件,他们会进行平分。
![](http://ww4.sinaimg.cn/large/577d3ebejw1f12eflpn09j20m40fxwfx.jpg)
如果你想控制部件的绝对大小,你可以设置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来理解它对部件位置的影响。
![](http://ww3.sinaimg.cn/large/577d3ebejw1f12efx6cvej20m40fxmy6.jpg)
#### 六、为布局添加一个背景
经常被询问的关于布局的一个问题是:
如何为一个布局添加一个背景图片/颜色/视频/...
布局本质上没有可视的元素:默认情况下,他们没有画布指令。但是你可以添加画布指令到一个布局实例,正如添加一个背景颜色:
```
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()
```
运行效果如下:
![](http://ww2.sinaimg.cn/large/577d3ebejw1f12egajhsuj20k00fkdgf.jpg)
添加颜色到背景用一个**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()
```
结果如下:
![](http://ww1.sinaimg.cn/large/577d3ebejw1f12egoq63rj20m80gotbt.jpg)
在子类中定义背景,确保它被用在每一个定制布局的实例中。
现在,为了添加一个图片或颜色到内置的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()
```
结果如下:
![](http://ww3.sinaimg.cn/large/577d3ebejw1f12ehaeg9ej20k00g0myr.jpg)
由于我们重载了网格布局的规则,任何应用该类的地方都会显示图片。
一个动画背景如何显示呢?
你可以设置绘画指令,像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)**。
### 下节预告:编程向导:4.8图形