Kivy Kv Design Language 入门指南:用声明式 .kv 文件快速构建跨平台 GUI

发布时间:2026/9/21 1:54:13
Kivy Kv Design Language 入门指南:用声明式 .kv 文件快速构建跨平台 GUI Kivy Kv Design Language 入门指南用声明式 .kv 文件快速构建跨平台 GUI【免费下载链接】kivyOpen source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS项目地址: https://gitcode.com/gh_mirrors/ki/kivy导读Kv Design Language简称 Kv 语言是 Kivy 内置的一套专为 GUI 设计打造的声明式描述语言。它让你可以用一份独立的.kv文件描述界面结构与样式把「界面设计」和「应用逻辑」彻底分离从而快速原型化、快速迭代并让界面代码在大规模应用中保持可维护性。读完本文你将掌握 Kv 语言的最小语法规则、缩进、属性赋值、三种加载方式、动态类复用技巧以及如何把.kv中的控件接入 Python 代码直接上手编写自己的第一个 Kivy 应用界面。本文以 Kivy 官方入门文档 doc/sources/gettingstarted/rules.rst 为骨架展开并参考完整语法指南 doc/sources/guide/lang.rst 与仓库源码进行深化。为什么需要一门「设计语言」随着应用规模增长直接用 Python 代码手工构建控件树、显式声明绑定binding会变得越来越冗长且难以维护。Kv 语言正是针对这一痛点而生它允许你以声明式declarative的方式创建控件树并自然地让控件属性之间、控件属性与回调之间产生绑定关系。它同时支持非常快的原型开发和敏捷的 UI 调整也促进了应用逻辑与用户界面的分离——这正是「关注点分离」separation of concerns原则在 GUI 领域的体现。在 Kivy 中界面设计与应用逻辑可以放在两个不同文件中逻辑.py负责数据、业务与事件处理界面.kv负责布局、样式与声明式绑定。Kv 语言的最小示例一个登录界面Kivy 官方入门文档给出了一个极其精简的示例完整展示了 Kv 语言的三个核心语法要素LoginScreen: # 每个类都可以用这样的规则rule在 kv 文件中表示 GridLayout: # 这样把你的控件/布局添加到父级注意缩进 rows: 2 # 这样设置控件/布局的每个属性LoginScreen:是类规则class rule它描述了LoginScreen这个类的所有实例的外观与行为GridLayout:是子控件实例声明缩进表示它是LoginScreen的子节点rows: 2是属性赋值右侧的表达式会被 Kivy 持续观察值变化时自动重新求值。仅凭这 3 行你就已经掌握了 Kv 语言的全部核心概念。文档中配套的示意图doc/sources/images/gs-lang.png展示了左侧 Kv 代码与右侧真实渲染界面的一一对应关系图中代码扩展版还展示了更多常用写法LoginScreen: GridLayout: rows: 2 cols: 1 spacing: 10 padding: 10 Label: text: User Name: TextInput: id: username password: False Label: text: Password: TextInput: id: password password: True从图中可以直观看到左侧声明式代码如何映射为右侧的登录界面GridLayout的rows/cols/spacing/padding如何控制行列排布与间距Label与TextInput如何成对出现id如何为控件命名以便引用password: True如何让输入内容以密文显示。如何加载 Kv 代码Kv 代码有两种标准加载方式源码实现见 kivy/app.py 与 kivy/lang/builder.py。方式一按命名约定自动加载Kivy 会寻找与你的 App 类同名转为小写、去掉末尾的App后缀的.kv文件。例如MyApp - my.kvApp.run()首次运行时若尚未构建控件树会调用App.load_kv()自动查找并加载该文件见 kivy/app.py。查找规则是.kv文件必须与 App 类定义所在的.py文件位于同一目录。例如main.py中定义了class ShowcaseApp(App)Kivy 就会在main.py所在目录寻找showcase.kv。如果该文件定义了根控件root widget它会被挂到 App 的root属性上作为应用控件树的根基ClassName: # 这是根控件root widget ...需要注意的是源码注释中明确说明load_kv由run()调用因此在run()之前例如在__init__中创建的控件不会获得该 kv 文件定义的样式而build()是在load_kv()之后才被调用的。方式二通过 Builder 显式加载你也可以用kivy.lang.Builder直接加载字符串或文件。若加载内容定义了根控件方法会将其返回from kivy.lang import Builder root_widget Builder.load_file(path/to/file.kv) # 从文件加载 root_widget Builder.load_string(kv_string) # 从字符串加载Builder.load_string()在解析后会把规则注册进全局规则表并实例化根控件返回见 kivy/lang/builder.py。它支持rulesonlyTrue参数用于只加载规则、禁止出现根控件也支持filename参数为字符串指定伪文件名便于之后用Builder.unload_file()卸载同一批规则。load_file()默认按 UTF-8 编码读取且从 3.0.0 版本起同时支持str与pathlib.Path路径见 kivy/lang/builder.py。规则Rule体系根规则、类规则与动态类规则一份 Kv 源码由若干「规则」组成用于描述某个控件的内容。你可以在一个 kv 文件中拥有1 个根规则root rule直接以控件类名开头无缩进后跟冒号会被设置为 App 实例的root属性Widget:任意数量的类规则class rule以 包裹类名后跟冒号定义该类的所有实例的外观与行为MyWidget:任意数量的动态类规则dynamic class rule新类名基类名:的形式见下文「动态类」。规则使用缩进来界定层级与 Python 一致官方建议每个缩进层级使用 4 个空格遵循 PEP 8 的缩进约定。Kv 语言内置三个关键字关键字含义app始终指向当前应用实例App 对象root指向当前规则中的根控件self始终指向当前控件本身在解析器实现中app被注册为一个ProxyApp代理对象按需解析为当前正在运行的应用实例见 kivy/lang/parser.py。此外解析器还在全局命名空间中预置了常用度量单位与工具例如dp、sp、pt、inch、cm、mm以及rgba因此你可以在 kv 表达式中直接写font_size: 25sp、rgba: 1, .3, .8, .5这样的值见 kivy/lang/parser.py。特殊语法访问 Python 模块与全局值Kv 语言提供两条指令用于为整个 kv 上下文定义值。#:import导入 Python 模块或类#:import name x.y.z #:import isdir os.path.isdir #:import np numpy等价于 Python 代码from x.y import z as name from os.path import isdir import numpy as np#:set设置全局值#:set name value等价于 Python 代码name value实例化子控件声明式控件树在规则内部声明某个类的实例即可将其作为子控件加入父控件MyRootWidget: BoxLayout: Button: Button:上面定义根控件是MyRootWidget的实例它有一个BoxLayout子控件该布局下再有两个Button子控件。对应的 Python 等价代码是root MyRootWidget() box BoxLayout() box.add_widget(Button()) box.add_widget(Button()) root.add_widget(box)相比之下Kv 版本显然更易读、易写。属性赋值声明式绑定在 Python 中你可以通过关键字参数在创建时指定控件行为例如grid GridLayout(cols3)。在 Kv 中直接在规则内设置属性即可GridLayout: cols: 3这里的值会被当作 Python 表达式求值且表达式中用到的所有属性都会被观察observed从而自动建立绑定。比如 Python 中若要实现「data 变化时自动更新列数」grid GridLayout(colslen(self.data)) self.bind(datagrid.setter(cols))在 Kv 中只需要一行并保持自动更新GridLayout: cols: len(root.data)这种自动重新求值机制在底层由Builder的绑定逻辑实现表达式被编译后解析器通过正则与 AST 分析出其中引用的key.value形式watched_keys当任一被观察属性变化时call_fn会重新eval该表达式并setattr更新属性见 kivy/lang/parser.py 与 kivy/lang/builder.py。对于类似self.a.b.c的链式属性Builder.update_intermediates还支持在中间属性变化时动态解绑/重绑后续链路见 kivy/lang/builder.py。命名约定控件类名应以大写字母开头属性名应以小写字母开头遵循 PEP 8 命名规范。事件绑定on_语法与argsKv 中使用:语法把回调关联到事件Widget: on_size: my_callback()事件分发的参数可以用args关键字引用TextInput: on_text: app.search(args[1])Kv 会自动为控件的每个属性生成对应的on_事件。例如TextInput的focus属性其自动生成的on_focus事件可以在 kv 中这样处理TextInput: on_focus: print(args)复杂表达式同样受支持并且其中的依赖属性都会建立绑定pos: self.center_x - self.texture_size[0] / 2., self.center_y - self.texture_size[1] / 2.该表达式监听了center_x、center_y与texture_size其中任一变化都会触发重新求值并更新pos。在解析器实现中凡属性名以on_开头的赋值都会被归类为事件处理器handler而非普通属性见 kivy/lang/parser.py其回调表达式以exec模式编译并可访问args变量。扩展 Canvas在 Kv 中绘制图形指令Kv 语言可以直接定义控件的 canvas 指令且当属性值变化时指令会自动更新MyWidget: canvas: Color: rgba: 1, .3, .8, .5 Line: points: zip(self.data.x, self.data.y)你也可以使用canvas.before与canvas.after来插入绘制顺序不同的指令层。解析器在遇到canvas、canvas.before、canvas.after三个特殊属性时会递归解析其子指令并分别存入控件的canvas_root、canvas_before、canvas_after见 kivy/lang/parser.py。引用控件id、weakref 陷阱与 ids 查找在控件树中经常需要访问其他控件。Kv 语言通过id提供这一能力——可以把id理解为「只能在 Kv 语言内部使用的类级变量」MyFirstWidget: Button: id: f_but TextInput: text: f_but.state MySecondWidget: Button: id: s_but TextInput: text: s_but.stateid的作用域被限制在其声明所在的规则内因此上例中s_but无法在MySecondWidget规则之外访问。警告给id赋值时值不是字符串不能加引号。正确写法id: value错误写法id: value。id 是 weakref不是强引用id保存的是控件的弱引用weakref因此仅靠存储id并不能防止控件被垃圾回收。官方文档给出了一个会触发ReferenceError: weakly-referenced object no longer exists的典型反例MyWidget: label_widget: label_widget Button: text: Add Button on_press: root.add_widget(label_widget) Button: text: Remove Button on_press: root.remove_widget(label_widget) Label: id: label_widget text: widget即使MyWidget中存有label_widget引用由于它只是弱引用一旦移除按钮被点击移除对该控件的直接引用、窗口被缩放触发垃圾回收导致label_widget被销毁再点击添加按钮就会抛出ReferenceError。正确做法是持有直接引用即使用id.__self__或label_widget.__self__MyWidget: label_widget: label_widget.__self__在 Python 代码中访问 Kv 控件ObjectProperty 与 ids假设my.kv中有MyFirstWidget: # 两个变量可以同名因为 id 只在 kv 中可见不会产生唯一性冲突 txt_inpt: txt_inpt Button: id: f_but TextInput: id: txt_inpt text: f_but.state on_text: root.check_status(f_but)在myapp.py中class MyFirstWidget(BoxLayout): txt_inpt ObjectProperty(None) def check_status(self, btn): print(button state is: {state}.format(statebtn.state)) print(text input text is: {txt}.format(txtself.txt_inpt))txt_inpt在类中先被声明为ObjectProperty(None)此时self.txt_inpt是NoneKv 中的txt_inpt: txt_inpt会把该属性更新为id为txt_inpt的TextInput实例。此后self.txt_inpt在类的任何位置都持有该控件引用例如在check_status函数中使用。与之相对也可以像f_but那样把 id 直接传给需要它的函数。另一种更简洁的方式是使用ids查找字典。Kv 文件解析完成后Kivy 会把所有带id的控件收集进self.ids字典属性Marvel Label: id: loki text: loki: I AM YOUR GOD! Button: id: hulk text: press to smash loki on_release: root.hulk_smash()class Marvel(BoxLayout): def hulk_smash(self): self.ids.hulk.text hulk: puny god! self.ids[loki].text loki: _!!! # 等价的下标语法由于self.ids是字典类型属性你还可以遍历它for key, val in self.ids.items(): print(key{0}, val{1}.format(key, val))最佳实践虽然self.ids很简洁但官方文档指出更推荐使用ObjectProperty方式——它创建的是直接引用访问更快、语义更显式。动态类复用样式消灭重复代码当多个控件需要完全相同的属性设置时逐个重复书写既冗长又易错。例如MyWidget: Button: text: Hello world, watch this text wrap inside the button text_size: self.size font_size: 25sp markup: True Button: text: Even absolute is relative to itself text_size: self.size font_size: 25sp markup: True Button: text: Repeating the same thing over and over in a comp fail text_size: self.size font_size: 25sp markup: True Button:使用动态类可以消除全部重复。只需一行规则新类名基类名:即可在 Kv 侧动态创建继承自基类的新类MyBigButtonButton: text_size: self.size font_size: 25sp markup: True MyWidget: MyBigButton: text: Hello world, watch this text wrap inside the button MyBigButton: text: Even absolute is relative to itself MyBigButton: text: repeating the same thing over and over in a comp fail MyBigButton:这个仅靠规则声明创建的类继承了Button允许你为它的所有实例统一修改默认值并建立绑定完全不需要在 Python 侧新增任何代码。在实现上解析器收集动态类定义后Builder会通过Factory.register()将新类注册进控件工厂见 kivy/lang/builder.py。多类共享样式逗号分隔的类规则当多个类需要同一份 Kv 样式时可以用逗号分隔类名一次性声明共享规则。例如原本两份几乎相同的规则MyFirstWidget: Button: on_press: root.text(txt_inpt.text) TextInput: id: txt_inpt MySecondWidget: Button: on_press: root.text(txt_inpt.text) TextInput: id: txt_inpt可以合并为MyFirstWidget,MySecondWidget: Button: on_press: root.text(txt_inpt.text) TextInput: id: txt_inpt只要在声明中用逗号隔开类名所有列出的类都会获得相同的 Kv 属性。解析器通过正则, *切分类名列表来处理这种声明见 kivy/lang/parser.py。完整实战用 Kv 语言实现「视图与逻辑分离」Kivy 语言的目标之一就是分离「表现层」与「逻辑层」布局由.kv文件负责逻辑由.py文件负责。仓库中的 examples/guide/designwithkv/main.py 与 examples/guide/designwithkv/controller.kv 提供了一个可直接运行的完整范例。逻辑放在main.pyimport kivy kivy.require(1.0.5) from kivy.uix.floatlayout import FloatLayout from kivy.app import App from kivy.properties import ObjectProperty, StringProperty class Controller(FloatLayout): Create a controller that receives a custom widget from the kv lang file. Add an action to be called from the kv lang file. label_wid ObjectProperty() info StringProperty() def do_action(self): self.label_wid.text My label after button press self.info New info text class ControllerApp(App): def build(self): return Controller(infoHello world) if __name__ __main__: ControllerApp().run()Controller类定义了两个属性info接收文本与label_wid接收 Label 控件同时定义do_action()方法它会同时修改info文本与label_wid控件中的文本。布局放在controller.kv没有对应.kv文件时程序也能运行但屏幕上什么都看不到——因为Controller只是FloatLayout自身没有任何子控件。为它创建controller.kv后ControllerApp运行时就会自动加载它#:kivy 1.0 Controller: label_wid: my_custom_label BoxLayout: orientation: vertical padding: 20 Button: text: My controller info is: root.info on_press: root.do_action() Label: id: my_custom_label text: My label before button press这个示例包含了三个关键机制从 Controller 读取数据text: My controller info is: root.info中info属性一旦改变表达式自动重新求值Button 文本随之更新。向 Controller 注入控件id: my_custom_label给 Label 命名label_wid: my_custom_label把该 Label 实例交给Controller的label_wid属性。创建自定义回调on_press: root.do_action()中root与self是可随处使用的保留关键字——root代表规则内的顶层控件self代表当前控件。规则内声明的任意 id 也可以像root/self一样直接使用例如Button: on_press: root.do_action(); my_custom_label.font_size 18运行main.py后controller.kv会被加载Button 与 Label 随即显示并响应触摸事件。这正是「一个标签 一个按钮」背后蕴含的完整数据流属性绑定自动刷新、id 注入实现跨层通信、事件回调驱动逻辑。小结与延伸阅读Kv 语言的完整要素可以概括为一张速查表要素语法说明根规则Widget:无缩进的类名 冒号成为 App 的root类规则MyWidget:定义某类所有实例的外观与行为动态类MyBtnButton:在 Kv 侧动态创建基类的子类多类共享A,B:逗号分隔多类共享同一份样式属性绑定cols: 3/text: root.info值作为 Python 表达式求值依赖属性自动观察事件绑定on_press:/on_text:用args引用事件参数on_前缀自动生成特殊关键字app/root/self全局应用实例 / 规则根控件 / 当前控件导入与全局值#:import/#:set访问 Python 模块、设置全局常量引用控件id:ObjectProperty/self.ids注意 id 是 weakref需用__self__保活Canvascanvas:/canvas.before/canvas.after声明式绘制指令属性变化自动更新度量单位dp/sp/pt/inch/cm/mm/rgba解析器内置可直接在表达式中使用至此你已经完成了 Kivy Kv 语言从最小语法到完整实战的入门。若想深入了解语言各组成部分的完整描述、高级用法与限制可以参考 Kivy 完整语言指南 doc/sources/guide/lang.rst以及语言模块源码 kivy/lang/init.py、解析器 kivy/lang/parser.py 和规则构建器 kivy/lang/builder.py。此外examples/guide/designwithkv/ 中的示例可以直接运行体验examples/kv/ 目录下还有大量展示各种控件与 canvas 用法的.kv示例文件可供参考。【免费下载链接】kivyOpen source UI framework written in Python, running on Windows, Linux, macOS, Android and iOS项目地址: https://gitcode.com/gh_mirrors/ki/kivy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考