分类:Kivy:修订间差异
无编辑摘要 |
|||
| 第500行: | 第500行: | ||
center_y: root.center_y | center_y: root.center_y | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== [[Kv 设计语言|4 Kv 设计语言]] === | |||
;__强显目录__ | ;__强显目录__ | ||
2025年12月24日 (三) 01:53的版本
Kivy速成教程
1、引言
Kivy是用Python编写的开源跨平台GUI框架,用于开发多平台应用程序(如Windows、macOS、Linux、Android、iOS等)。
它基于OpenGL ES 2构建,采用自绘UI的方式替代依赖原生系统组件,因此能在不同平台上保持界面和交互逻辑的高度一致性。Kivy引入了专门的Kv语言,允许开发者将UI布局与业务逻辑分离,简化复杂界面的设计流程;同时原生支持多点触控、手势识别等交互特性,非常适合开发游戏、多媒体展示、交互式工具等对界面动态性要求较高的应用。此外,Kivy拥有活跃的社区生态,提供了丰富的扩展库(如KivyMD等Material Design风格组件库),帮助开发者快速实现美观且功能完善的跨平台应用。
使用 Kivy,您可以创建运行以下应用程序:
- *台式电脑:macOS、Linux、BSD Unix、Windows。
- iOS 设备:iPad、iPhone。
- 安卓设备:平板电脑、手机。
- 任何其他支持TUIO的触控专业/自制设备 (有形用户界面对象)
Kivy 赋予你一次编写代码并运行它的自由 不同平台上的现状。
您将使用 Kivy:
- 学习:使用Kivy语言编程的基础知识。
- 探索:Kivy 框架。
- 创建:一个简单的跨平台应用程序。
- 打包:适用于您选择的平台。
最后, Deploy您将学习如何在您选择的设备上部署。
2、安装 Kivy
本文Kivy版本基于Kivy 2.3.1 ,支持 Python 版本 3.8 - 3.13
使用 pip安装
安装 Kivy 最简单的方法就是pip。
在安装 Kivy 之前,需要预装 Python 和 pip。 然后,启动一个新终端。在终端中, 更新pip以及其他安装 依赖项使您的最新版本如下所示(对于Linux用户,您可能需要替代python3而不是python并添加一个--user flag。
Windows:
python -m pip install --upgrade pip setuptools virtualenv
linux:
python3 -m pip install --upgrade pip setuptools virtualenv
创建虚拟环境
创建一个新的虚拟环境 适用于您的 Kivy 项目。虚拟环境可以避免可能的安装冲突。 与其他 Python 版本和软件包兼容。虽然是可选的 ,但强烈建议这样做 :
在当前目录下创建名为 kivy_venv 虚拟环境:
python -m venv kivy_venv
激活虚拟环境
source kivy_venv/bin/activate (linux)
安装Kivy
最简单的方法是安装当前稳定版本的 kivy ,还可以选择 kivy_examples 使用 kivy 团队提供的 PyPi wheels。只需执行以下操作:
python -m pip install "kivy[base]" kivy_examples
通过源安装
略
安装预发布版、预编译的 wheel 文件
要安装 Kivy 最新预发布版本的预编译 wheel 文件,而不是当前稳定版本,请在 pip 命令中添加 --pre 标志:
python -m pip install --pre "kivy[base]" kivy_examples
只有当 Kivy 发布了开发版本时,此操作才会安装该开发版本。 PyPi 。或者,也可以从 Kivy 服务器安装最新的 Nightly wheel 包:
python -m pip install kivy --pre --no-deps --index-url https://kivy.org/downloads/simple/
python -m pip install "kivy[base]" --pre --extra-index-url https://kivy.org/downloads/simple/
开发安装
略
3. 第一个应用
入门
我们先来创建一个非常简单的 Kivy 应用并让它运行起来。创建一个用于存放游戏的目录,并在其中创建一个名为 main.py 的文件。
from kivy.app import App
from kivy.uix.widget import Widget
class PongGame(Widget):
pass
class PongApp(App):
def build(self):
return PongGame()
if __name__ == '__main__':
PongApp().run()
运行该应用程序。此时应该只会显示一个黑色窗口。我们创建了一个非常简单的 Kivy App ,它创建了 PongGame Widget 类的一个实例,并将其作为应用程序 UI 的根元素返回。此时,您可以将其想象成一个 Widget 层级树。Kivy 会将这个 Widget 树放置在默认的 Window 窗口中。下一步,我们将通过定义 PongGame widget 外观来绘制 Pong 的背景和分数。
添加简单图形
我们将使用 .kv 文件来定义 PongGame 类的外观和风格。由于我们的 App 类名为 PongApp ,我们可以直接在应用程序运行时自动加载同一目录下的 pong.kv 文件。因此,请创建一个名为 ``pong.kv`` 的新文件,并添加以下内容。
#:kivy 1.0.9
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
Label:
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: "0"
Label:
font_size: 70
center_x: root.width * 3 / 4
top: root.top - 50
text: "0"
kv 文件的名称(例如 pong.kv)必须与应用程序的名称(例如 PongApp)匹配(App 结尾之前的部分)。
解释 Kv 文件语法
在进行下一步之前,您可能需要仔细查看一下我们刚刚创建的 kv 文件的内容,弄清楚它的作用。
每个 kv 文件都必须包含第一行。它应该以 #:kivy 开头。 然后是一个空格,以及它所针对的 Kivy 版本(以便 Kivy 可以创建它)。 请确保您拥有至少所需的版本,或者处理向后兼容性问题。
之后,我们开始定义适用于所有 PongGame 规则。 实例:
<PongGame>:
...
与 Python 类似,kv 文件使用缩进来定义嵌套代码块。用 < 和 > 字符括起来的类名定义的代码块是一个 Widget 规则。它将应用于指定类的任何实例。例如,如果将示例中的 PongGame 替换为 Widget ,则所有 Widget 实例都将包含垂直线和两个 Label 组件,因为该规则将应用于所有 Widget 实例。
在规则部分,您可以添加各种代码块来定义它们将应用到的控件的样式和内容。您可以:
- 设置属性值
- 添加子控件
- 定义一个区域,您可以在其中添加定义小部件渲染方式的图形指令。
<PongGame> 规则中的第一个块是一个 canvas块:
<PongGame>:
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
这个 canvas 块表示 PongGame 组件应该绘制一些图形图元。在本例中,我们在 canvas 中添加一个矩形。我们将矩形的 pos 坐标设置为组件水平中心左侧 5 像素,y 坐标设置为 0。矩形的宽度设置为 10 像素,高度设置为组件的高度。这样定义图形的好处在于,当值表达式中使用的任何组件的属性发生变化时,渲染的矩形也会自动更新。
最后添加的两个部分看起来非常相似。它们都向 PongGame 组件添加了一个 Label 组件作为子组件。目前,这两个组件的文本都设置为 “0” 。我们将在实现逻辑后将其与实际分数关联起来,但由于我们设置了更大的 font_size 并将其相对于根组件定位,因此标签看起来已经不错了。可以在子组件块中使用 root 关键字来引用规则所应用的父/根组件(在本例中为 PongGame ):
<PongGame>:
# ...
Label:
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: "0"
Label:
font_size: 70
center_x: root.width * 3 / 4
top: root.top - 50
text: "0"
添加球
我们已经有了一个基本的乒乓球场地,但还需要玩家和球。我们先从球开始。我们添加一个新的 创建一个小部件作为我们的球,并让它弹跳起来。
PongBall类
class PongBall(Widget):
# velocity of the ball on x and y axis
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
# referencelist property so we can use ball.velocity as
# a shorthand, just like e.g. w.pos for w.x and w.y
velocity = ReferenceListProperty(velocity_x, velocity_y)
# ``move`` function will move the ball one step. This
# will be called in equal intervals to animate the ball
def move(self):
self.pos = Vector(*self.velocity) + self.pos
以下是用于将球画成白色圆圈的 kv 规则:
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
为了使一切正常运行,您还需要添加以下导入语句: Property classes使用以及 Vector
以下是此步骤的完整更新后的 Python 代码和 kv 文件:
- main.py:
from kivy.app import App from kivy.uix.widget import Widget from kivy.properties import NumericProperty, ReferenceListProperty from kivy.vector import Vector class PongBall(Widget): velocity_x = NumericProperty(0) velocity_y = NumericProperty(0) velocity = ReferenceListProperty(velocity_x, velocity_y) def move(self): self.pos = Vector(*self.velocity) + self.pos class PongGame(Widget): pass class PongApp(App): def build(self): return PongGame() if __name__ == '__main__': PongApp().run()
- pong.kv:不仅添加了控件规则,还在控件规则中添加了子控件
#:kivy 1.0.9 <PongBall>: size: 50, 50 canvas: Ellipse: pos: self.pos size: self.size <PongGame>: canvas: Rectangle: pos: self.center_x - 5, 0 size: 10, self.height Label: font_size: 70 center_x: root.width / 4 top: root.top - 50 text: "0" Label: font_size: 70 center_x: root.width * 3 / 4 top: root.top - 50 text: "0" PongBall: center: self.parent.center
添加球体动画
Clock.schedule_interval(game.update, 1.0/60.0)
这行代码将导致游戏对象的 update 函数每 1/60 秒调用一次
对象属性/引用(Object Properties/References)
还有另一个问题。我们想确保乒乓球拥有它的 move 函数会被定期调用,但由于我们只是通过 KV 文件中 PongGame 类的 KV 规则添加了球对象,因此我们的代码中没有任何对球对象的引用。我们游戏的唯一引用是应用程序构建方法中返回的那个。
既然我们要做的功能不只是移动球(例如让球反弹到墙壁上,以及之后反弹到玩家的球拍上),那么我们的 PongGame 类可能无论如何都需要一个 update 方法。此外,鉴于 我们已经有了对游戏对象的引用,我们可以轻松地通过执行update 方法让它执行新任务。
class PongGame(Widget):
def update(self, dt):
# call ball.move and other stuff
pass
class PongApp(App):
def build(self):
game = PongGame()
Clock.schedule_interval(game.update, 1.0/60.0)
return game
However, that still doesn’t change the fact that we don’t have a reference to the PongBall child widget created by the kv rule. To fix this, we can add an ObjectProperty to the PongGame class, and hook it up to the widget created in the kv rule. Once that’s done, we can easily reference the ball property inside the update method and even make it bounce off the edges:
然而,这仍然无法改变一个事实就是,我们没有由 kv 规则创建的 PongBall 子组件的引用。为了解决这个问题,我们可以添加一个 ObjectProperty 将其添加到 PongGame 类,并将其连接到在kv规则中创建的控件。完成之后,我们就可以轻松地引用球的属性了。 在 update 方法内部,甚至可以使其从边缘反弹:
class PongGame(Widget):
ball = ObjectProperty(None)
def update(self, dt):
self.ball.move()
# bounce off top and bottom
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.velocity_y *= -1
# bounce off left and right
if (self.ball.x < 0) or (self.ball.right > self.width):
self.ball.velocity_x *= -1
<PongGame>:
ball: pong_ball
# ... (canvas and Labels)
PongBall:
id: pong_ball
center: self.parent.center
别忘了在 kv 文件中进行连接,给子控件指定一个 id,并将 PongGame 的 ball ObjectProperty 设置为该 id。
至此,所有组件都已连接完毕,球可以开始弹跳了。如果您跟着我们一起编写代码,可能会疑惑为什么球没有移动。这是因为球的 x 轴和 y 轴速度都被设置为 0。在下面的代码示例中,我们在 PongGame 类中添加了一个名为 serve_ball 方法,并在应用程序的 build 方法中调用了该方法。该方法会为球设置一个随机的 x 轴和 y 轴速度,并重置球的位置,以便我们稍后在玩家得分后重置球的位置。
连接输入事件
添加玩家并对触摸输入做出反应。
在 Kivy 中,控件可以通过实现以下功能来响应输入: on_touch_down , on_touch_move 和 on_touch_up 方法。默认情况下,Widget 类 它通过调用其所有组件上的相应方法来实现这些方法。 子控件会将事件传递下去,直到其中一个子控件返回 True 。
Pong 游戏非常简单。球拍只需要上下移动。事实上,它非常简单,我们甚至不需要让玩家组件自己处理事件。我们只需要实现 on_touch_move 函数即可。 PongGame 类,并根据触摸发生在屏幕左侧还是右侧来设置左侧玩家或右侧玩家的位置。
检查 on_touch_move 处理程序:
def on_touch_move(self, touch):
if touch.x < self.width/3:
self.player1.center_y = touch.y
if touch.x > self.width - self.width/3:
self.player2.center_y = touch.y
我们会记录每个玩家的得分。 NumericProperty 。 PongGame 的得分标签 通过更改 NumericProperty score 来保持分数更新,这反过来又会更新 PongGame 子标签的 text 属性。这种绑定是因为 Kivy properties 会自动绑定到其对应 kv 文件中的任何引用。当球从边线飞出时,我们将通过更改 PongGame 类中的 update 方法来更新分数并再次发球。PongPaddle 的属性是 PongPaddle 。 该类还实现了一个 bounce_ball 方法,以便球能够反弹。 根据击球位置的不同,效果也不同。以下是代码PongPaddle类:
class PongPaddle(Widget):
score = NumericProperty(0)
def bounce_ball(self, ball):
if self.collide_widget(ball):
speedup = 1.1
offset = 0.02 * Vector(0, ball.center_y-self.center_y)
ball.velocity = speedup * (offset - ball.velocity)
附全代码: main.py:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import (
NumericProperty, ReferenceListProperty, ObjectProperty
)
from kivy.vector import Vector
from kivy.clock import Clock
class PongPaddle(Widget):
score = NumericProperty(0)
def bounce_ball(self, ball):
if self.collide_widget(ball):
vx, vy = ball.velocity
offset = (ball.center_y - self.center_y) / (self.height / 2)
bounced = Vector(-1 * vx, vy)
vel = bounced * 1.1
ball.velocity = vel.x, vel.y + offset
class PongBall(Widget):
velocity_x = NumericProperty(0)
velocity_y = NumericProperty(0)
velocity = ReferenceListProperty(velocity_x, velocity_y)
def move(self):
self.pos = Vector(*self.velocity) + self.pos
class PongGame(Widget):
ball = ObjectProperty(None)
player1 = ObjectProperty(None)
player2 = ObjectProperty(None)
def serve_ball(self, vel=(4, 0)):
self.ball.center = self.center
self.ball.velocity = vel
def update(self, dt):
self.ball.move()
# bounce off paddles
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
# bounce ball off bottom or top
if (self.ball.y < self.y) or (self.ball.top > self.top):
self.ball.velocity_y *= -1
# went off to a side to score point?
if self.ball.x < self.x:
self.player2.score += 1
self.serve_ball(vel=(4, 0))
if self.ball.right > self.width:
self.player1.score += 1
self.serve_ball(vel=(-4, 0))
def on_touch_move(self, touch):
if touch.x < self.width / 3:
self.player1.center_y = touch.y
if touch.x > self.width - self.width / 3:
self.player2.center_y = touch.y
class PongApp(App):
def build(self):
game = PongGame()
game.serve_ball()
Clock.schedule_interval(game.update, 1.0 / 60.0)
return game
if __name__ == '__main__':
PongApp().run()
pong.kv:
#:kivy 1.0.9
<PongBall>:
size: 50, 50
canvas:
Ellipse:
pos: self.pos
size: self.size
<PongPaddle>:
size: 25, 200
canvas:
Rectangle:
pos: self.pos
size: self.size
<PongGame>:
ball: pong_ball
player1: player_left
player2: player_right
canvas:
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
Label:
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str(root.player1.score)
Label:
font_size: 70
center_x: root.width * 3 / 4
top: root.top - 50
text: str(root.player2.score)
PongBall:
id: pong_ball
center: self.parent.center
PongPaddle:
id: player_left
x: root.x
center_y: root.center_y
PongPaddle:
id: player_right
x: root.width - self.width
center_y: root.center_y