架构概览¶
我们想花一点时间,从软件工程的角度解释我们是如何设计Kivy的。这是理解所有组件如何协同工作的关键。如果你只看代码,很可能已经有个大致概念,但鉴于这种方法对大多数用户来说确实令人望而生畏,本节将更详细地解释实现的基本理念。你可以跳过本节,稍后再查阅,但我们建议至少略读一遍,以获得一个粗略的概览。
Kivy 由几个构建块组成,我们稍后将进行解释。以下是架构的图形概览:
核心提供者与输入提供者¶
理解Kivy内部机制的一个关键思想是模块化和抽象化。我们尝试抽象基本任务,如打开窗口、显示图像和文本、播放音频、从摄像头获取图像、拼写校正等。我们称这些为*核心*任务。这使得API既易于使用又易于扩展。最重要的是,它允许我们为你的应用运行的具体场景使用——我们所谓的——特定提供者。例如,在macOS、Linux、BSD Unix和Windows上,不同的核心任务有不同的原生API。一段代码,一边使用这些特定API与操作系统通信,另一边与Kivy交互(作为中间通信层),这就是我们所说的*核心提供者。为每个平台使用专门的核心提供者的优势在于,我们可以充分利用操作系统提供的功能,并尽可能高效地运作。这也给用户提供了选择。此外,通过使用随任何平台一起分发的库,我们有效减少了Kivy发行版的大小,并使打包更加容易。这也使得将Kivy移植到其他平台变得更加简单。Android移植就大大受益于此。
我们在输入处理方面遵循相同的概念。*输入提供者*是一段代码,用于为特定输入设备(如苹果触控板、TUIO或鼠标模拟器)添加支持。如果您需要为新的输入设备添加支持,只需提供一个新类,从您的设备读取输入数据并将其转换为Kivy基本事件即可。
图形。¶
Kivy的图形API是我们对OpenGL的抽象。在最底层,Kivy使用OpenGL发出硬件加速的绘图命令。然而,编写OpenGL代码可能会有些令人困惑,尤其是对新手而言。因此,我们提供了图形API,让您可以使用OpenGL中并不存在的简单隐喻(如Canvas、Rectangle等)来绘制内容。
我们所有的控件本身都使用这一图形API,该API在C语言层面实现,以追求性能优化。
图形API的另一个优势在于,它能够自动优化你的代码所发出的绘制命令。如果你不是调优OpenGL的专家,这一点尤其有用。这能在许多情况下让你的绘制代码更加高效。
当然,如果你更喜欢,仍然可以使用原始的OpenGL命令。我们在所有设备上针对的版本是OpenGL 2.0 ES(GLES2),因此如果你想保持跨平台兼容性,我们建议你仅使用GLES2函数。
核心¶
核心包中的代码提供了常用的功能,例如:
- 时钟
您可以使用时钟来调度定时器事件。支持一次性定时器和周期性定时器。
- 缓存
如果你需要缓存经常使用的内容,可以使用我们的类来实现,而无需自己编写。
- 手势检测
我们提供了一个简单的姿态识别器,可用于检测各种笔画,如圆形或矩形。您可以训练它来识别您自己的笔画。
- Kivy 语言
Kivy语言用于轻松高效地描述用户界面。
- 属性
这些并不是你在Python中可能熟悉的普通属性。它们是Kivy自己的属性类,用于将你的控件代码与用户界面描述连接起来。
UIX(控件与布局)¶
UIX 模块包含了常用的控件和布局,您可以直接复用它们来快速创建用户界面。
- 控件
Widget 是您添加到程序中以提供某种功能的用户界面元素。它们可能可见,也可能不可见。例如文件浏览器、按钮、滑块、列表等。Widget 会接收 MotionEvent。
- 布局
您可以使用布局来排列控件。当然,您也可以自行计算控件的位置,但通常使用我们现成的布局之一会更加方便,例如网格布局或盒布局。您还可以嵌套布局。
模块¶
如果你曾使用过现代网页浏览器并为其添加过一些插件,那么你已经了解了我们模块类背后的基本概念。模块可用于向Kivy程序注入功能,即使原始作者并未包含这些功能。
一个示例是,一个模块始终显示当前应用程序的FPS,并绘制一个随时间变化的FPS图表。
你也可以编写自己的模块。
输入事件(触摸)¶
Kivy 抽象了不同的输入类型和来源,如触摸、鼠标、TUIO 或类似设备。所有这些输入类型的共同点是,你可以将屏幕上的二维位置与任何单个输入事件关联起来。(还有其他输入设备,如加速度计,你无法轻易为设备的倾斜等找到二维位置。这类输入单独处理。以下我们描述的是前几种类型。)
所有这些输入类型都由Touch()类的实例表示。(请注意,这不仅仅指手指触摸,也包括所有其他输入类型。为了简便起见,我们称之为*Touch*。可以将其视为*触摸*用户界面或屏幕的东西。)一个触摸实例或对象可以处于三种状态之一。当触摸进入这些状态之一时,您的程序会被告知事件发生。触摸可以处于的三种状态是:
- 向下
触摸事件仅在首次出现的那一刻触发一次。
- 移动。
触摸可以在此状态下保持无限长的时间。触摸在其生命周期内不必处于此状态。每当触摸的二维位置发生变化时,就会发生“移动”。
- 向上
触摸事件最多只会触发一次“抬起”,或者永远不会触发。实际上,你几乎总会收到一个“抬起”事件,因为没有人会永远把手指按在屏幕上,但这并不能保证。如果你知道用户将使用的输入源,你就能判断是否可以依赖这一状态的进入。
控件与事件分发¶
在GUI编程中,术语*widget*通常用来描述用户与之交互的程序部分。在Kivy中,widget是一个接收输入事件的对象,它不一定需要在屏幕上有可见的表示。所有widget都排列在一个*widget树*中(这是一种计算机科学课程中常见的树形数据结构):一个widget可以有任意数量的子widget,也可以没有。树的最顶端恰好有一个*根widget*,它没有父widget,所有其他widget直接或间接地成为这个widget的子widget(因此得名“根”)。
当有新的输入数据时,Kivy 会为每个触摸发送一个事件。控件树中的根控件首先接收该事件。根据触摸的状态,会向根控件分发 on_touch_down、on_touch_move 或 on_touch_up 事件(以触摸作为参数),这会导致根控件对应的 on_touch_down、on_touch_move 或 on_touch_up 事件处理器被调用。
树中的每个控件(包括根控件)都可以选择消化或传递事件。如果事件处理器返回 True,则表示事件已被消化并妥善处理,该事件将不再进行任何进一步处理。否则,事件处理器会通过调用其父类对应事件处理器的实现,将事件传递给自身的子控件。这一过程一直向上延伸到基础的 Widget 类,在其触摸事件处理器中,除了将触摸传递给子控件外,不做任何其他操作:
# This is analogous for move/up:
def on_touch_down(self, touch):
for child in self.children[:]:
if child.dispatch('on_touch_down', touch):
return True
这实际上比乍看起来要简单得多。下一节将给出一个示例,展示如何利用这一点快速创建美观的应用程序。
很多时候,你会希望限制屏幕上某个部件监听触摸的*区域*。你可以使用部件的`collide_point()`方法来实现这一点。只需将触摸的位置传递给它,如果触摸位于“监听区域”内,则返回`True`,否则返回`False`。默认情况下,此方法检查屏幕上由部件的`pos`(位置;x和y)和`size`(宽度和高度)描述的矩形区域,但你可以在自己的类中覆盖此行为。