四、编程向导(4.7部件)

555 查看

编程向导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)

按钮被添加到布局:按钮的父属性被设置为layoutlayout将会把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_hintpos_hint属性来确定它们子部件的尺寸和位置。

(一)盒子布局(BoxLayout)

盒子布局以相邻的方式(或平行或垂直)来安排他们的子部件,填满所有的空间。子部件的size_hint属性能被用来改变被允许的比例或设置固定的尺寸。


box layout
(二)网格布局(GridLayout)

网格布局排列部件在网格内。你必须至少制定网格的一个维度,这样Kivy才能计算元素的尺寸及如何排列它们。


grid layout
(三)堆叠布局(StackLayout)

堆叠布局一个接一个的排列部件,但是在一个维度上使用了一组尺寸,不要试图使它们填满整个空间。它常用来显示有同样尺寸的子部件。


stack layout
(四)锚点布局(AnchorLayout)

一种简单的布局,它仅关注子部件的位置。它允许在一个相对于布局的边的位置放置子部件。size_hint被忽略。


anchor layout
(五)浮动布局(FloatLayout)

浮动布局允许放置任意位置和尺寸的子部件。默认size_hint(1, 1)将会使每一个子部件有同样的尺寸作为整个布局,所以,如果你有多个子部件,你可能想改变这个值。你能设置set_hint到(None, None)来使用绝对的尺寸。同样也可以使用pos_hint设置位置。


float layout
(六)相对布局(RelativeLayout)

有点像FloatLayout,除了子部件的位置是相对于布局位置,而不是屏幕位置。
查看每个布局的文档,可以用更深入的了解。

[size_hint]和[pos_hint]:

  • [floatlayout]
  • [boxlayout]
  • [gridlayout]
  • [stacklayout]
  • [relativelayout]
  • [anchorlayout]

size_hint是一个关于size_hint_xsize_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,这样部件的widthheight特征值就会使用。

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图形