四、编程向导(4.7部件)

作者: gthank | 来源:发表于2016-02-18 11:23 被阅读1069次

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

    相关文章

      网友评论

        本文标题:四、编程向导(4.7部件)

        本文链接:https://www.haomeiwen.com/subject/xfmmkttx.html