💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 编程向导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图形