### 編程向導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)**。
### 下節預告:編程向導:4.8圖形