Kivy - 快速指南



Kivy - 入门

Kivy 是一个开源的 Python 库。它允许您构建具有自然用户界面 (NUI) 的多点触控应用程序。使用 Kivy,您可以开发跨平台应用程序。相同的代码,只需编写一次,即可部署到不同的各种操作系统平台,例如 Windows、macOS、Linux、Android 和 iOS。

Python 中流行的 GUI 框架

Kivy 是 Python 生态系统中众多 GUI 框架之一。一些用于构建桌面 GUI 应用程序的流行 Python 库有:

  • Tkinter − Tkinter 包捆绑在 Python 的标准库中。它是 Tcl/Tk GUI 工具包的标准 Python 接口。

  • PyQt5 − 此库是 Qt GUI 工具包的 Python 移植版本。您可以在 此处 访问我们关于 PyQt5 的完整教程。

  • WxPython − WxPython 库允许 Python 程序员访问 WxWidgets,这是一个开源的 GUI 工具包,最初是用 C++ 编写的。要了解更多关于 WxPython 的信息,请点击 此处

  • Kivy − Kivy 是一个 Python 库,可帮助您为 Windows、Linux、iOS 和 Android 构建跨平台 GUI 应用程序。Kivy 支持触控输入。Kivy GUI 框架中的所有控件都能够处理多点触控手势。

Kivy 拥有强大的图形和多媒体功能。Kivy 应用程序可以支持音频、视频、动画以及 2D 和 3D 图形。

Python Kivy 的主要特性

以下是 Python Kivy 的一些主要特性:

  • Kivy 支持触控输入。Kivy GUI 框架中的所有控件都能够处理多点触控手势。

  • Kivy 的全面 GUI 控件和强大的布局管理使设计吸引人的界面变得轻而易举。

  • Kivy 拥有强大的图形和多媒体功能。这使得在应用程序中集成 2D 和 3D 图形、动画、音频和视频组件成为可能。

  • Kivy 支持各种类型的输入设备。包括触摸、鼠标和手势。

  • Kivy API 可以访问移动设备的硬件组件,例如相机、GPS 等。

  • Kivy 使用 OpenGL ES 2 图形库,并基于顶点缓冲对象和着色器。

  • Kivy 依靠 Cython 进行核心实现,并依靠 SDL2(简单直接媒体层)进行底层多媒体和输入处理。

要在配备 Windows、Linux 或 iOS 操作系统的桌面上部署 Kivy 应用程序,可使用 PyInstaller 构建可分发文件。要为 Android 构建 APK,您需要使用 Android Studio 和 Buildozer 实用程序。

Kivy 语言

Kivy 使用一种称为 Kivy 语言(有时也称为 Kv 语言)的特殊声明式语言来构建 Kivy 应用程序的用户界面布局。它用于将应用程序的设计方面与其编程逻辑分离。设计以“.kv”扩展名的文本文件编写。Kivy 框架会自动加载“.kv”文件并根据其中给定的规范构建 UI。

Kivy 库的初始版本于 2011 年发布。目前,Kivy 2.2 版本已于 2023 年 5 月发布。

Kivy - 安装

要构建 Kivy 应用程序,您需要在计算机上安装 Python。最新的稳定版本 Kivy 2.2.0 正式支持 Python 3.7 到 3.11 版本。如果尚未安装 Python,请从 Python 官方网站下载适用于您的操作系统和体系结构的最新 Python 版本的安装程序:https://www.pythonlang.cn/downloads/

Python 虚拟环境

Python 建议使用虚拟环境来避免与其他 Python 版本和包发生冲突。

虚拟环境允许我们为特定项目创建 Python 的隔离工作副本,而不会影响外部设置。我们将使用 Python 标准库中的“venv”模块来创建虚拟环境。PIP 默认包含在 Python 3.4 或更高版本中。

创建虚拟环境

使用以下命令在 Windows 中创建虚拟环境:

C:\users\user\>python -m venv c:\kivyenv

在 Ubuntu Linux 上,在创建虚拟环境之前,如果需要,请更新 APT 存储库并安装“venv”。

mvl@GNVBGL3:~ $ sudo apt update && sudo apt upgrade -y
mvl@GNVBGL3:~ $ sudo apt install python3-venv

然后,使用以下命令创建虚拟环境:

mvl@GNVBGL3:~ $ sudo python3 -m venv kivyenv

激活虚拟环境

您需要激活虚拟环境。在 Windows 上,使用以下命令:

C:\>cd kivyenv
C:\kivyenv>scripts\activate
(kivyenv) C:\kivyenv>

在 Ubuntu Linux 上,使用以下命令激活虚拟环境:

mvl@GNVBGL3:~$ cd kivyenv
mvl@GNVBGL3:~/kivyenv$ source bin/activate
(myenv) mvl@GNVBGL3:~/kivyenv$

使用 pip 实用程序安装 Kivy

安装任何 Python 包最简单的方法是使用“pip”实用程序。Python 3 安装附带“pip”安装程序。激活虚拟环境后,在 Windows 中的 CMD 终端或 Linux 终端中使用以下命令:

pip3 install "kivy[base]" kivy_examples

这将安装 Kivy 包及其最少依赖项。“kivy_examples”包是可选的。使用“full”选项而不是“base”可以启用音频/视频支持。

安装 Kivy 的依赖库

SDL2(简单直接媒体层)是 Kivy 的主要依赖项。在 Windows 操作系统上,当您使用“pip”实用程序时,SDL2 会自动安装。但是,对于 Linux 和 macOS,您需要单独安装 SDL2。

在 macOS 上,您可以使用 Homebrew 通过在终端中运行以下命令来安装 SDL2:

brew install sdl2

如果在 Linux 操作系统上,请使用相应的包管理器安装 SDL2。例如,在 Ubuntu Linux 机器上,可以使用以下命令完成此操作:

sudo apt-get install libsdl2-dev

此外,您可能需要安装其他依赖项,例如“gstreamer”和“Pillow”,以实现 Kivy 的某些特定功能。

验证 Kivy 安装

要验证 Kivy 是否已正确安装,请启动 Python 交互式 shell 并导入包。控制台显示 Kivy 的依赖项也已导入。

>>> import kivy
[INFO] [Logger] Record log in C:\Users\mlath\.kivy\logs\kivy_23-05-26_0.txt
[INFO] [deps] Successfully imported "kivy_deps.gstreamer" 0.3.3
[INFO] [deps] Successfully imported "kivy_deps.angle" 0.3.3
[INFO] [deps] Successfully imported "kivy_deps.glew" 0.3.1
[INFO] [deps] Successfully imported "kivy_deps.sdl2" 0.6.0
[INFO] [Kivy] v2.2.0
[INFO] [Kivy] Installed at "c:\kivyenv\Lib\site-packages\kivy\__init__.py"
[INFO] [Python] v3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
[INFO] [Python] Interpreter at "c:\kivyenv\Scripts\python.exe"
[INFO] [Logger] Purge log fired. Processing...
[INFO] [Logger] Purge finished!

您还可以使用“pip freeze”命令获取已安装的所有包的列表:

(kivyenv) C:\kivyenv>pip3 freeze
certifi==2023.5.7
charset-normalizer==3.1.0
docutils==0.20.1
idna==3.4
Kivy==2.2.0
kivy-deps.angle==0.3.3
kivy-deps.glew==0.3.1
kivy-deps.gstreamer==0.3.3
kivy-deps.sdl2==0.6.0
Kivy-examples==2.2.0
Kivy-Garden==0.1.5
Pillow==9.5.0
Pygments==2.15.1
pypiwin32==223
pywin32==306
requests==2.31.0
urllib3==2.0.2

Kivy - 架构

阅读本章以了解 Kivy 框架的设计架构。一方面,Kivy 提供各种控件,允许用户与应用程序交互;另一方面,它与各种硬件设备交互,例如鼠标、TUIO、音频和视频流等。中间层包含用于处理触摸输入、音频和视频、图形指令和文本输入的驱动程序或提供程序。

这是 Kivy 框架的官方架构图:

Kivy Architecture

核心提供程序

Kivy 架构的一个重要特性是“模块化”和“抽象”。打开窗口、读取音频和视频流、加载图像等操作是任何图形应用程序中的核心任务。Kivy 通过为控制硬件的驱动程序提供易于实现的 API 来抽象这些核心任务。

Kivy 使用特定提供程序来处理应用程序运行的操作系统。每个操作系统(Windows、Linux、macOS 等)都有自己的本机 API 用于不同的核心任务。它们充当操作系统与 Kivy 之间的中间通信层。因此,Kivy 充分利用了操作系统公开的功能来提高效率。

使用特定于平台的库可以减小 Kivy 发行版的大小并简化打包过程。这也使得将 Kivy 移植到其他平台变得更容易。Android 移植从中受益匪浅。

输入提供程序

输入提供程序是一段代码,用于添加对特定输入设备的支持。Kivy 中内置支持的不同输入设备包括:

  • Android 操纵杆输入提供程序
  • Apple 的触控板
  • TUIO(有形用户界面对象)
  • 鼠标模拟器
  • HIDInput

要添加对新输入设备的支持,请提供一个新类,该类从您的设备读取输入数据并将其转换为 Kivy 基本事件。

图形

OpenGL 是 Kivy 框架整个图形 API 的基础。Kivy 使用 OpenGL 指令发出硬件加速的绘图命令。Kivy 通过定义易于使用的功能来消除编写 OpenGL 命令的困难部分。

Kivy 使用 OpenGL 2.0 ES 版本(GLES 或嵌入式系统的 OpenGL),您可以使用它进行跨平台开发。

核心库

Kivy 框架的以下组成部分提供了高级抽象:

  • 时钟 − 时钟 API 可帮助您安排定时器事件。支持一次性定时器和周期性定时器。

  • 手势检测 − 多点触控界面的重要需求。手势识别器可以检测各种类型的笔划,例如圆形或矩形。您甚至可以训练它来检测您自己的笔划。

  • Kivy语言 − kivy语言用于轻松高效地描述用户界面。这使得应用程序设计与开发应用程序逻辑分离。

  • 属性 − Kivy独特的属性类概念(它们与Python类中的属性不同)是将您的窗口小部件代码与用户界面描述链接的元素。

UIX

Kivy的用户界面由窗口小部件和布局构建。

  • 窗口小部件是您添加到应用程序中的 UI 元素,以提供某种功能。窗口小部件的示例包括按钮、滑块、列表等等。窗口小部件接收MotionEvents。

  • 多个窗口小部件排列在合适的布局中。Kivy提供布局类,以满足各种用途的窗口小部件放置需求。例如网格布局或盒子布局。您还可以嵌套布局。

事件分发

术语“窗口小部件”在几乎所有图形工具包中都用于 UI 元素。任何接收输入事件的对象都是窗口小部件。一个或多个窗口小部件排列成树状结构。

Kivy应用程序窗口只能容纳一个根窗口小部件,但根窗口小部件可以在树状结构中包含其他窗口小部件。因此,窗口小部件之间存在“父-子-兄弟”关系。

每当发生新的输入事件时,窗口小部件树的根窗口小部件首先接收该事件。根据触控的状态,事件会向下传播到窗口小部件树。

树中的每个窗口小部件都可以处理事件或将其传递给层次结构中的下一个窗口小部件。如果窗口小部件吸收并处理事件,则应返回True,以便停止其向下传播到树中,并且不会对该事件进行进一步处理。

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(宽度 & 高度)描述的矩形区域。

Kivy - 文件语法

Kivy框架提供了一种简洁的声明式方法来定义窗口小部件结构和外观,并使用Kivy语言(也称为Kv语言)。它是一种声明式语言,专门用于构建Kivy应用程序中的用户界面。其主要优点是可以将UI设计与用Python编写的应用程序逻辑分离。

UI设计定义在文本文件中,该文件必须具有“.kv”扩展名。它包含应用程序窗口中窗口小部件的层次序列。该文件采用树状结构,显示窗口小部件之间的父子兄弟关系。在每个窗口小部件下方,都指定了其属性、事件和事件处理程序。

kv设计语言在创建“.kv”文件时规定以下约定,以便Python和Kivy框架能够识别和加载相应的窗口小部件结构:

  • 文件名必须小写

  • 它必须与应用程序中的主类匹配。此类继承自App类。

  • 如果类的名称以“app”或“App”结尾(例如,HelloApp),则“.kv”文件必须从其名称中排除“app”。这意味着,对于HelloApp类,“.kv”文件的文件名必须为“hello.kv”。

  • “.kv”文件必须与Python应用程序文件(.py)所在的同一文件夹中。

在使用“.kv”文件时,App类不会覆盖build()方法。只需使用pass语句声明一个类就足够了。当调用run()方法时,Kivy会自动从相应的“.kv”文件中加载UI。

让我们首先从HelloApp类中删除build()方法:

示例

from kivy.app import App
class HelloApp(App):
   pass
app = HelloApp()
app.run()

用户界面定义在同一文件夹中的“hello.kv”文件中。我们有一个垂直方向的顶级BoxLayout,在其下方放置了两个标签。将以下脚本保存为“hello.kv”文件

BoxLayout:
   orientation: 'vertical'
   Label:
      text: 'Python Kivy Tutorial'
      font_size: '30pt'
   Label:
      text: 'From TutorialsPoint'
      font_size: '50'
      color: (1,0,0,1)

现在,如果您运行“hello.py”程序,它将产生以下输出:

输出

kivy file syntax

在后面的章节中,我们将学习如何在“.kv”文件中向窗口小部件添加事件处理程序。

Kivy - 应用

使用Kivy框架编写的应用程序由继承“kivy.app.App”类的类的对象表示。调用此对象的run()方法启动应用程序,并进入无限事件循环。

应用程序GUI通过覆盖App类中的build()方法或提供相应的“.kv”文件来设置。

应用程序配置

如果要提供一个或多个参数的自定义配置,则在调用App类的build_config()方法时将创建config.ini文件。

这是一个build_config()方法的示例。它将两个参数的值存储在“ini”文件的“section1”中。“ini”文件的文件名将与应用程序类相同(如果它有“App”后缀,则不包含)。因此,如果您的应用程序类是“HelloApp”,则将创建“ini”文件作为“hello.ini”。当调用build()方法时,将加载此文件中的参数。

def build_config(self, config):
   config.setdefaults('section1', {
      'Company': 'TutorialsPoint',
      'year': '2023'
   })

添加部分后,将在包含“hello.py”文件的同一目录中创建“hello.ini”文件。

如下所示在build()方法中加载并使用配置设置:

def build(self):
   config = self.config
   l1 = Label(text="© {} Year {}".format(
      config.get('section1', 'company'),
      config.getint('section1', 'year')),
      font_size=40)
   return l1

运行应用程序时,将通过读取“config”文件填充标签。

示例

以下是完整的程序:

from kivy.app import App
from kivy.uix.label import Label
from kivy.core.window import Window

class HelloApp(App):
   Window.size = (720, 300)

   def build_config(self, config):
      config.setdefaults('section1', {
         'Company': 'TutorialsPoint',
         'year': '2023'
      })

   def build(self):
      config = self.config
      l1 = Label(text="© {} Year {}".format(
         config.get('section1', 'company'),
         config.getint('section1', 'year')),
         font_size=40)
      return l1

app = HelloApp()
app.run()

输出

运行应用程序时,它将生成以下窗口作为输出

Kivy Applications

查找应用程序文件夹中创建的“hello.ini”文件。使用文本编辑器打开后,它显示以下内容:

[section1]
company = TutorialsPoint
year = 2023

App类中的实例方法

App类定义了以下实例方法:

  • build() − 此方法初始化应用程序,并且仅调用一次。如果此方法返回一个窗口小部件(树),则它将用作根窗口小部件并添加到窗口中。

  • build_config() − 此方法在初始化应用程序之前构造ConfigParser对象。根据您在此处放置的任何默认部分/键/值配置,将在本地目录中创建“ini”文件。

  • load_config() − 此函数返回包含应用程序配置的ConfigParser。

  • load_kv() − 如果在此应用程序之前尚未构建窗口小部件树,则在第一次运行应用程序时将调用此方法。然后,此方法会在与包含应用程序类的文件相同的目录中查找匹配的“kv”文件。

  • pause() − 此方法导致应用程序暂停。

  • run() − 调用时,此方法以独立模式启动应用程序。

  • stop() −此方法停止应用程序。

  • on_pause() − 这是当请求暂停模式时调用的事件处理程序方法。如果它返回True,则应用程序可以进入暂停模式,否则应用程序将停止。

  • on_resume() − 从暂停模式恢复应用程序的事件处理程序方法。

  • on_start() − 此方法是“on_start”事件的事件处理程序。它在初始化后(在调用build()之后)但在应用程序开始运行之前触发。

  • on_stop() − 应用程序运行结束时(即窗口即将关闭)触发的“on_stop”事件。此方法处理on_stop事件。

Kivy - Hello World

让我们从使用Kivy构建一个简单的“Hello World”应用程序开始。请按照以下步骤操作:

要开发Kivy应用程序,您需要从“kivy.app”模块导入App类。

from kivy.app import App

使用App作为其基类的类的对象表示应用程序。要设计界面,请覆盖build()方法,该方法返回一个根窗口小部件。现在,让我们在build()方法中放置一个pass语句。

class HelloApp(App):
   def build(self):
      pass

接下来,实例化上述“HelloApp”类:

app = HelloApp()

App类的run()方法启动无限事件循环。它显示一个当前没有任何窗口小部件的空白应用程序窗口。

app.run()

现在让我们向根窗口小部件添加一个不可编辑的标签,其中包含“Hello World”标题。为此,我们必须从“kivy.uix.label”模块导入Label类。更改build()方法,如下面的程序所示。

Python Kivy中的Hello World

以下是使用Kivy打印“Hello World”的完整代码:

示例

from kivy.app import App
from kivy.uix.label import Label

class HelloApp(App):
   def build(self):
   l1 = Label(text="Hello World", font_size=50)
   return l1

app = HelloApp()
app.run()

Label对象可以使用许多属性进行配置。在这里,我们只设置text和font_size属性。

从命令行运行上述代码(hello.py):

python hello.py

Kivy在终端中生成更多日志文本

[INFO ] [Factory] 190 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil (img_ffpyplayer ignored)
[INFO ] [Text ] Provider: sdl2
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] GLEW initialization succeeded
[INFO ] [GL ] Backend used <glew>
[INFO ] [GL ] OpenGL version <b'4.6.0 - Build 31.0.101.3959'>
[INFO ] [GL ] OpenGL vendor <b'Intel'>
[INFO ] [GL ] OpenGL renderer <b'Intel(R) Iris(R) Xe Graphics'>
[INFO ] [GL ] OpenGL parsed version: 4, 6
[INFO ] [GL ] Shading version <b'4.60 - Build 31.0.101.3959'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [Base ] Start application main loop
[INFO ] [GL ] NPOT texture support is available

运行此应用程序时,您将获得默认的Kivy应用程序窗口,其中包含一个带有“Hello World”文本的标签。

Kivy Hello World

您可以按“X”按钮关闭窗口并停止正在运行的应用程序。

Kivy中的布局

在上面的程序中,我们在应用程序的根树中只使用了一个窗口小部件,即Label。如果要放置多个窗口小部件,则需要将它们添加到布局中,然后从build()方法返回布局对象。Kivy支持各种类型的布局,例如BoxLayout、FlowLayout、AnchorLayout等等。

让我们设计界面,以便在垂直BoxLayout对象中添加两个标签。标签一个接一个地添加。HelloApp类的build()方法将相应地更改。

示例

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout

class HelloApp(App):
   def build(self):
      lo = BoxLayout(orientation='vertical')
      l1 = Label(text="Hello World", font_size=50)
      l2 = Label(text = "From TutorialsPoint",
         font_size=30,
         color = (1,0,0,1))
      lo.add_widget(l1)
      lo.add_widget(l2)
      return lo

app = HelloApp()
app.run()

输出

运行上述程序以获得以下输出

Kivy Hello World

Kivy - 应用生命周期

Kivy应用程序从执行到停止会经历不同的阶段。下图显示了不同的阶段:

Kivy App Life Cycle

现在让我们详细讨论每个阶段:

初始化UI

Kivy框架中的App类是表示Kivy应用程序的类。创建App对象是应用程序生命周期中的第一步。

from kivy.app import App

声明App类的子类,并覆盖build()方法。

from kivy.app import App

class MyApp(App):
   def build(self):
      #UI Design

它通过调用build()方法或借助“.kv”文件来构建应用程序的UI。如果需要,应用程序的配置将从相应的“.ini”文件中加载。

事件循环

加载用户界面后,App对象进入无限事件循环。

if __name__ == '__main__':
   MyApp().run()

界面中组装的各种窗口小部件现在吸收用户交互(例如按钮点击或文本输入),并根据相应的事件处理程序做出响应。响应用户交互,任何窗口小部件或应用程序的状态都可能被修改。

要运行应用程序,请从OS终端执行以下命令:

Python MyApp.py

虽然你可以在 Windows 或 Linux 上以这种方式运行你的 Kivy 应用,但在 Android 上运行它可能需要一些额外的步骤。对于 Android,你应该构建一个 APK(Android 软件包工具包)。

你应该使用 **Buildozer**,这是一个自动化整个构建过程的工具。它安装了 python-for-android 的所有先决条件,包括 android SDK 和 NDK,然后构建一个可以自动推送到设备的 APK。Buildozer 目前仅在 Linux 和 macOS 上运行(对于 Windows,请在机器上激活 WSL,然后从 WSL 中使用 Buildozer)。

暂停/恢复

当应用正在运行时,可以使其暂停。例如,如果应用程序窗口被最小化,或者设备本身进入睡眠模式,暂停模式有助于节省资源。

Kivy 有一个 on_pause() 事件处理程序。当请求暂停模式时,它会被调用。如果你的应用可以进入暂停模式,则应返回 True,否则返回 False,你的应用程序将停止。你无法控制应用程序何时进入此模式。它由操作系统决定,主要用于移动设备(Android/iOS)和调整大小。

应用可以从暂停的地方恢复运行。

当你的应用程序从暂停模式恢复时,Kivy 的 on_resume() 事件处理程序会被调用。

恢复时,OpenGL 上下文可能已损坏/释放。在这里你可以重建一些 OpenGL 状态。

停止

当用户通过调用应用代码中的适当方法关闭应用时。在应用程序运行终止之前,会进行所有清理操作。

Kivy - 事件

Kivy 是一个 Python 库,可帮助你构建跨平台的 GUI 应用程序。任何 GUI 应用程序都是事件驱动的,其中程序的流程不是顺序的(从上到下),而是由用户与界面上的小部件交互来决定的。用户操作,例如点击按钮、“从列表中选择一个项目”或“从可用的单选按钮中选择一个选项”等,称为 **事件**。

基于 GUI 的程序会预料到其环境内外可能发生的所有事件,并在某个事件发生时(以及如果发生)将其分派给相应的处理程序函数。

Kivy Events

当调用 Kivy 的 App 对象的 run() 方法时,应用程序启动一个“事件监听”循环并触发相应的回调函数,每个事件类型一个。

全局事件分发器

Kivy 框架包含 EventDispatcher 类。它将事件对象分派到窗口小部件树,并且事件通过窗口小部件层次结构传播。当处于处理事件位置的窗口小部件时,其关联的回调处理程序将被触发。**Widget、Animation** 和 **Clock** 类是事件分发器的示例。

Clock 是一个全局事件分发器,允许你在特定时间间隔安排和触发事件。它定义了诸如 'schedule_once()' 和 'schedule_interval()' 之类的方法,以注册在特定延迟后或以定期间隔调用的函数或方法。此机制对于处理定时事件、动画更新以及应用中的其他重复任务很有用。

示例

在下面的示例中,我们在窗口小部件树中放置了一个标签和两个按钮。标题为“开始”的按钮安排每隔一秒定期发生一个事件。schedule_interval() 函数使用以下语法编写:

Clock.schedule_interval(callback, timeout)

另一方面,标题为“停止”的第二个按钮调用 unscheduled() 方法,该方法删除已安排的事件。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class DemoApp(App):
   def build(self):
      lo = BoxLayout(orientation='vertical')
      self.l1 = Label(text="0", font_size=100)
      self.b1 = Button(text="start", font_size=100, color=(0, 0, 1, 1))
      
      self.b1.bind(on_press=self.onstart)
      self.b2 = Button(text="Stop", font_size=100, color=(1, 0, 0, 1))
      self.b2.bind(on_press=self.onstop)
      lo.add_widget(self.l1)
      lo.add_widget(self.b1)
      lo.add_widget(self.b2)
      return lo

   def onstart(self, event):
      print("started")
      Clock.schedule_interval(self.update_label, 1)

   def onstop(self, event):
      Clock.unschedule(self.update_label)

   def update_label(self, event):
      self.l1.text = str(int(self.l1.text) + 1)

if __name__ == '__main__':
   DemoApp().run()

输出

运行以上程序。标签最初显示“0”。点击“开始”按钮。标签开始每秒在其上递增数字。按“停止”按钮取消安排事件。

Kivy Events

窗口小部件事件

大多数 Kivy 窗口小部件都具有内置的事件处理功能。实际上,每个窗口小部件都设计为处理特定类型的事件。例如,Button 窗口小部件处理由点击它引起的事件。你可以为窗口小部件上发生的特定事件(例如按钮点击、触摸事件或键盘事件)注册事件处理程序。

通常,事件处理程序在窗口小部件的类或 App 类中定义为方法。它们通常以“on_”为前缀,后跟事件的名称。例如,按钮按下事件的“on_press”。

当事件发生时,Kivy 会自动调用相应的事件处理程序方法,并将有关事件的相关信息作为参数传递。在事件处理程序中,你可以定义所需的行为或要执行的操作。

与窗口小部件关联的事件

下面列出了与一些最常用窗口小部件关联的事件:

按钮

  • **on_press** - 当按钮被按下时触发。

  • **on_release** - 当按钮被释放时触发。

  • **on_touch_down** - 当按钮上开始触摸事件时触发。

  • **on_touch_up** - 当按钮上结束触摸事件时触发。

TextInput

  • **on_text_validate** - 当用户完成文本输入编辑(通过按下 Enter 或 Return)时触发。

  • **on_focus** - 当文本输入获得或失去焦点时触发。

  • **on_text** - 每当输入字段中的文本更改时触发。

CheckBox

  • **on_active** - 当复选框被选中或取消选中时触发。

Slider

  • **on_value** - 当滑块的值更改时触发。

  • **on_touch_down** - 当滑块上开始触摸事件时触发。

  • **on_touch_up** - 当滑块上结束触摸事件时触发。

ToggleButton

  • **on_state** - 当切换按钮的状态更改(切换打开或关闭)时触发。

  • **on_press** - 当切换按钮被按下时触发。

ListView

  • **on_select** - 当列表视图中的项目被选中时触发。

FileChooser

  • **on_selection** - 当在文件选择器中选择文件或目录时触发。

Switch

  • **on_active** - 当开关切换打开或关闭时触发。

Video

  • **on_load** - 当视频加载完成时触发。

  • **on_play** - 当视频开始播放时触发。

  • **on_pause** - 当视频暂停时触发。

  • **on_stop** - 当视频停止播放时触发。

Spinner

  • **on_text** - 当从微调器中选择一个项目时触发。

ActionButton

  • **on_press** - 当操作按钮被按下时触发。

示例 - Kivy 中的事件处理

当我们解释 Kivy 框架中的每个窗口小部件时,将讨论这些事件。但是,在本节中,这里给出了两个事件处理示例。

示例 1

第一个示例显示了 Button 窗口小部件的“on_press”事件。在下面的代码中,我们有一个 Label 和一个 Button 窗口小部件,排列在一个 BoxLayout 中。为了处理 on_press 事件,我们将按钮与 DemoApp 类中定义的 onstart() 方法绑定

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class DemoApp(App):
   def build(self):
      lo = BoxLayout(orientation='vertical')
      self.l1 = Label(text="0", font_size=100)
      self.b1 = Button(text = "start", font_size = 100, color=(1,0,0,1))
      self.b1.bind(on_press=self.onstart)
      lo.add_widget(self.l1)
      lo.add_widget(self.b1)
      return lo

   def onstart(self, event):
      print ("started")
      self.l1.text = str(int(self.l1.text)+1)

if __name__ == '__main__':
   DemoApp().run()

输出

将以上代码保存为“demo.py”并从命令行或 Python IDE 运行它。程序以标签显示 0 作为其标题开始。每次单击按钮时,标题都会增加 1。

Kivy Events Start

示例 2

在此示例中,我们有一个 TextInput 窗口小部件和一个 Label。TextInput 绑定到 onkey() 方法。用户输入的每个按键都反映在 Label 上。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout

from kivy.clock import Clock
from kivy.core.window import Window

Window.size = (720,400)

class DemoApp(App):
   def build(self):
      lo = BoxLayout(orientation='vertical')
      self.t1 = TextInput(multiline=False)
      self.l1 = Label(font_size=50)
      self.t1.bind(text=self.onkey)

      lo.add_widget(self.t1)
      lo.add_widget(self.l1)
      return lo
      
   def onkey(self, event, value):
      print ("press")
      self.l1.text = value

if __name__ == '__main__':
   DemoApp().run()

输出

运行程序并在文本框中输入一些文本。当你输入时,Label 标题会更新。

Kivy Events Msg

还可以定义自定义事件和事件处理程序来处理特定于应用程序的事件。为此,你需要对 'EventDispatcher' 类进行子类化并定义所需属性和方法。

自定义事件可以使用类似于窗口小部件事件的 'trigger()' 和 'on_event_name' 模式进行分派和处理。

Kivy - 属性

属性是 Kivy 中的一个特殊类,允许你定义和管理窗口小部件或对象的属性。Property 类在“kivy.properties”模块中定义。你可以跟踪对这些属性的更改,并且它们允许你绑定回调函数以便在属性更改时执行。

Kivy 的属性类支持以下功能:

值检查/验证

每当将新值分配给属性时,都会根据验证约束对其进行检查,以防止错误。例如,OptionProperty 的验证将确保该值位于预定义的可能性列表中。NumericProperty 的验证将检查你的值是否为数字类型。

观察者模式

你可以指定属性值更改时应该发生什么。你可以将自己的函数绑定为属性更改的回调。例如,如果你希望在窗口小部件的 pos 属性更改时调用一段代码,则可以将一个函数绑定到它。

更好的内存管理

同一属性的实例在多个窗口小部件实例之间共享。

  • 需要注意的是,Property 对象与 Python 中的 property() 内置函数不同。

  • 必须在类级别声明属性对象,而不是在类的任何方法中。

  • 每个属性默认提供一个“on_<propertyname>”事件,每当属性的状态/值更改时都会调用该事件。

示例

让我们通过以下示例研究 Kivy 中 Property 的行为。App 类具有一个 NumericProperty 属性。NumericProperty 对象(值)绑定到 on_value_change() 方法。

class NumPropApp(App):
   value = NumericProperty(0)

   def on_value_change(self, instance, value):
      print(f"Value changed: {value}")
      self.l1.text = str(value)

在 build() 方法中,应用有一个 Label 和一个 Button 组装在一个垂直 BoxLayout 中。Button 响应 on_press 事件调用 onstart() 方法并将值加 1。

   def onstart(self, event):
      print ("started")
      self.value = self.value+1
   
   def build(self):
      lo = BoxLayout(orientation='vertical')
      self.l1 = Label(text=str(self.value), font_size = 50)
      self.b1 = Button(text = "start", font_size = 50)
      self.b1.bind(on_press=self.onstart)
      self.bind(value=self.on_value_change)
      lo.add_widget(self.l1)
      lo.add_widget(self.b1)
      return lo

由于“on_value_change()”方法在每次值更改时都会被调用,因此效果是每次按下按钮时,标签标题都会显示从“0”开始递增的数字。

以下是示例的**完整代码**:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import NumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class NumPropApp(App):
   value = NumericProperty(0)
   def on_value_change(self, instance, value):
      print(f"Value changed: {value}")
      self.l1.text = str(value)
   def onstart(self, event):
      print ("started")
      self.value = self.value+1
   def build(self):
      lo = BoxLayout(orientation='vertical')
      self.l1 = Label(text=str(self.value), font_size = 50)
      self.b1 = Button(text = "start", font_size = 50)
      self.b1.bind(on_press=self.onstart)
      self.bind(value=self.on_value_change)
      lo.add_widget(self.l1)
      lo.add_widget(self.b1)
      return lo

if __name__ == '__main__':
   NumPropApp().run()

输出

从命令行运行程序。按按钮查看标签上显示的数字每次都会增加。

Kivy Properties

属性类型

Kivy 提供以下属性类型:

**NumericProperty** - 处理数字值,例如整数和浮点数。它只接受 int 或 float 数字数据类型或可以转换为数字的字符串。

count = NumericProperty(0)

**StringProperty** - 用于处理字符串值。你可以使用“defaultvalue”参数对其进行初始化。

text = StringProperty("start")

**BoundedNumericProperty** - 此属性类似于 NumericProperty,但允许你为值定义最小和最大边界。它还支持 get_min() 和 get_max() 方法,分别返回最小和最大可接受值。

a = BoundedNumericProperty(1, min=0, max=100)

**BooleanProperty** - 处理布尔值(True 或 False)。defaultvalue 参数可以设置为 True 或 False。

active = BooleanProperty(False)

**ListProperty** - 此属性的值是 List 对象。将列表分配给 ListProperty 时,存储在属性中的列表是列表的浅拷贝,而不是原始列表。

colors = ListProperty([1, 0, 0, 1])

**ObjectProperty** - 处理单个对象实例。如果 rebind 参数设置为 True,则关联的 **kv** 规则将被重新评估,并且当任何中间属性更改时,所有属性都将被重新绑定。

person = ObjectProperty(None)

OptionProperty − 指定属性的默认值。它应该来自“Options”参数中给出的列表。例如:

state = OptionProperty("None", options=["On", "Off", "None"])

ReferenceListProperty − 此属性用于引用一个或多个其他类型的属性对象。

   x = NumericProperty(0)
   y = NumericProperty(0)
   z = ReferenceListProperty(x, y)

更改“z”的值将自动相应地更改“x”和“y”的值。如果读取“z”的值,它将返回一个包含“x”和“y”值的元组。

AliasProperty − 为现有属性提供别名或替代名称。

   def _get_width(self):
      return self.size
   def _set_width(self, value):
      self.size = value
   width = AliasProperty(_get_width, _set_width)

DictProperty − 用于定义具有多个参数作为字典键的对象的初始值。

   params = DictProperty({
      'xlog': False,
      'xmin': 0,
      'xmax': 100,
      'ylog': False,
      'ymin': 0,
      'ymax': 100,
      'size': (0, 0, 0, 0)
   })

VariableListProperty − 列表项并将其扩展到所需的列表大小。

obj = VariableListProperty(defaultvalue, length)

defaultvalue 参数指定列表的默认值。length 参数是一个整数,可以是 2 或 4。

ConfigParserProperty − ConfigParserProperty 允许您根据其他 Kivy 属性自动侦听和更改指定键的值。

ConfigParserProperty(defaultvalue, section, key, config)

ConfigParser 由多个部分组成,每个部分都有一些与这些键关联的键和值。

username = ConfigParserProperty('', 'info', 'name', None)

ColorProperty − 处理各种格式的色彩值,例如 RGB 或十六进制。此属性可以分配以下任何值:

  • 0-1 之间的一组 3 或 4 个浮点值(Kivy 默认值)

  • 格式为 #rrggbb 或 #rrggbbaa 的字符串

  • 表示颜色名称的字符串(例如,“red”、“yellow”、“green”)

Kivy - 输入

Kivy 框架能够接收和处理来自鼠标、触摸屏、陀螺仪、加速计等的不同类型的输入。大多数情况下,Kivy 会自动检测可用的硬件。但是,如果要支持自定义硬件,则需要相应地配置 Kivy。

不同输入源生成的所有事件都由相应的事件类表示。MotionEvent 是用于指向设备(触摸和非触摸事件)提供的事件的基本类。

  • 触摸事件 − 包含至少 X 和 Y 位置的运动事件。所有触摸事件都跨 Widget 树分派。

  • 非触摸事件 − 加速计就是一个非触摸事件的例子,因为它是一个连续的事件,没有位置。它从不启动或停止。这些事件不会跨 Widget 树分派。

Kivy 对输入应用后处理并对其进行分析以做出有意义的解释,例如:

  • 它是双击/三击检测吗?(根据距离和时间阈值)

  • 当硬件不准确时使事件更准确

  • 如果本机触摸硬件发送的事件位置几乎相同,则减少生成的事件数量

处理后,运动事件将分派到 Window。如果它只是一个运动事件,它将分派到 on_motion()。另一方面,如果它是触摸事件,触摸的 (x,y) 位置(0-1 范围)将按比例缩放至 Window 大小(宽度/高度),并分派到:

  • on_touch_down()
  • on_touch_move()
  • on_touch_up()

示例

在下面的示例中,我们定义了一个名为 widget 的新类,它继承自 Widget。我们需要使用以下语句导入 Widget 类:

from kivy.uix.widget import Widget

widget 类中有三个方法:

  • on_touch_down − 初始按下。

  • on_touch_move − 按下后以及按下期间的移动。

  • on_touch_up − 按下的“释放”。

class widget(Widget):
   def on_touch_down(self, touch):
      print("Down:",touch)
   def on_touch_move(self, touch):
      print("Move:",touch)
   def on_touch_up(self, touch):
      print("UP!",touch)

接下来,App 类的 build() 方法返回 widget() 对象。

class MotionApp(App):
   def build(self):
      return widget()

您可以通过点击并拖动屏幕来测试代码。您应该看到您执行的所有移动和按压的鼠标位置。

以下是完整代码。您可以保存并运行它:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class widget(Widget):
   def on_touch_down(self, touch):
      print("Down:",touch)
   def on_touch_move(self, touch):
      print("Move:",touch)
   def on_touch_up(self, touch):
      print("UP!",touch)

class MotionApp(App):
   def build(self):
      return widget()

if __name__ == '__main__':
   MotionApp().run()

输出

输出是一个空应用程序窗口,其中没有任何 UI 小部件。

在窗口中的任何位置单击鼠标。将捕获“on_touch_down”和“on_touch_up”事件,显示鼠标触摸的位置,如下所示:

Down: <MouseMotionEvent spos=(0.4228094575799722, 0.38596491228070173) pos=(304.0, 154.0)>
UP! <MouseMotionEvent spos=(0.4228094575799722, 0.38596491228070173) pos=(304.0, 154.0)>
Down: <MouseMotionEvent spos=(0.5730180806675939, 0.5137844611528822) pos=(412.0, 205.0)>
UP! <MouseMotionEvent spos=(0.5730180806675939, 0.5137844611528822) pos=(412.0, 205.0)>
Down: <MouseMotionEvent spos=(0.2517385257301808, 0.5588972431077694) pos=(181.0, 223.0)>
UP! <MouseMotionEvent spos=(0.2517385257301808, 0.5588972431077694) pos=(181.0, 223.0)>

MouseMotionEvent 的spos属性在 0-1 坐标系中提供相对位置。应用程序窗口的左下角对应于 (0,0),右上角对应于 (1,1)

pos属性显示鼠标单击的实际坐标。在上面的示例中,它相对于 (0,0) 位置向右 378.85 像素,向上 281.39 像素。

按住鼠标并将其移动到窗口中,您将获得 spos 和 pos 属性的瞬时变化值。例如:

Move: <MouseMotionEvent spos=(0.41863699582753827, 0.5338345864661654) pos=(376.3546592489569, 266.38345864661653)>
Move: <MouseMotionEvent spos=(0.4172461752433936, 0.531328320802005) pos=(375.1043115438108, 265.1328320802005)>
Move: <MouseMotionEvent spos=(0.41585535465924894, 0.5288220551378446) pos=(373.8539638386648, 263.88220551378447)>

事件配置文件

基于输入提供程序和使用的硬件类型,事件配置文件包含有关输入事件的更多信息。该配置文件是 MotionEvent 对象的设备特定属性。例如,触摸输入具有 (x,y) 位置,但可能还具有压力信息、斑点大小、加速度向量等。

通过在 touch_down 事件处理程序中添加以下语句,我们可以找出当前设备支持的功能。

def on_touch_down(self, touch):
   print(touch.profile)

输出将取决于设备类型。它可能是:

['pos', 'button']

或者,

['pos', 'angle']

配置文件值

以下是默认支持的一些配置文件值。

序号 配置文件值和描述
1 角度

二维角度。通过“a”属性访问。

2 按钮

鼠标按钮('left'、'right'、'middle'、'scrollup' 或 'scrolldown')。通过 button 属性访问。

3 标记 ID

标记或基准 ID。通过 fid 属性访问。

4 位置

二维位置。通过 x、y 或 pos 属性访问。

5 pos3d

三维位置。通过 x、y 或 z 属性访问。

6 压力

接触压力。通过 pressure 属性访问。

7 形状

接触形状。通过 shape 属性访问。

触摸形状

在 Kivy 中,触摸事件期间的交互区域由术语“触摸形状”表示。它指的是用于表示屏幕上触摸或触摸事件的几何形状。如果触摸具有形状,它将反映在“shape”属性中。

Kivy 支持不同的触摸形状,包括椭圆形、矩形、圆形和正方形。

双击/三击

在多点触控设备的上下文中,双击是在规定时间和距离内连续点击两次的操作。类似地,设备可以识别“三击”操作。

事件对象具有“is_double_tap”属性和“is_triple_tap”属性,两者都评估为 True 或 False。您可以测试当前触摸是否为双击之一:

def on_touch_down(self, touch):
   if touch.is_double_tap:
      print('Touch is a double tap!')
      print(' - interval is', touch.double_tap_time)
      print(' - distance between previous is', touch.double_tap_distance)

快速连续按下鼠标按钮两次。您可能会获得类似于以下所示的结果:

Touch is a double tap!
- interval is 0.17462420463562012
- distance between previous is 0.0

Kivy - 行为

在 Kivy 中,“kivy.uix.behaviors”模块定义了行为混合,也称为“可重用类”,它们为小部件提供额外的功能。它们封装了常见的功能,可以与多个小部件混合以扩展其行为。

  • 行为有助于保持代码模块化、可重用和可维护。它们允许您为标准 Kivy 小部件定义自己的实现,这些实现可以作为直接替换。

  • 行为混合的一个应用是将图像用作按钮。我们可以定义一个扩展 ButtonBehavior 的自定义类,使其响应“on_press”或“on_touch”等事件,以便图像本身可以充当按钮。在本章的后面,我们将看看将图像转换为按钮的示例。

“kivy.uix.behaviors”模块定义了几个混合。下面解释了一些最常用的类:

ButtonBehavior

此行为为小部件提供类似按钮的功能。它添加了诸如按下/释放视觉反馈、自动触发“on_press”和“on_release”事件以及处理触摸事件等功能。

它通常与 Button、ToggleButton 或需要类似按钮行为的自定义小部件一起使用。

DragBehavior

此行为类允许通过触摸输入拖动和移动小部件。它处理诸如 on_touch_down、on_touch_move 和 on_touch_up 等触摸事件以实现拖动功能。

它可用于在应用程序中创建可拖动的小部件。

FocusBehavior

此行为提供对管理小部件之间焦点的支持。它允许小部件接收键盘输入并处理与焦点相关的事件。

它可用于实现键盘导航和管理应用程序内的焦点遍历。

SelectableBehavior

此行为为小部件添加选择功能。它允许用户从一组可选小部件中选择一个或多个项目。它处理选择状态、视觉反馈和触发与选择相关的事件。

它通常与 ListView、RecycleView 或需要选择功能的自定义小部件一起使用。

ButtonBehavior 示例

我们现在将开发一个 Kivy 程序来实现 ButtonBehavior。我们使用 Kivy 的 Image 对象在 Kivy 窗口上显示图像。但是,要向其添加类似按钮的行为,我们首先定义一个名为 imgbtn 的自定义类,该类扩展了 Image 和 ButtonBehavior 类。

Image 类的 source 属性被分配一个字符串,该字符串是图像文件路径。然后我们覆盖 on_press() 方法,如下所示:

from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
      self.source = 'Logo.jpg'

   def on_press(self):
      print("Button pressed")

之后,定义了imgbtn类。让 App 类的 build() 方法返回其对象。

以下是可运行代码。您可以保存并运行它:

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
      self.source = 'Logo.jpg'

   def on_press(self):
      print("Button pressed")

class ImageApp(App):
   def build(self):
      return imgbtn()

if __name__ == '__main__':
   ImageApp().run()

输出

运行上述程序。它将在其中心显示一个带有图像的 Kivy 窗口:

Kivy Behaviors

请注意,图像本身充当按钮。要测试,请单击图像,它将在控制台上打印以下消息:

Button pressed

Kivy - 按钮

按钮是任何 GUI 库(包括 Kivy)中最重要的元素之一。按钮对象包含一个标签,通常用于指示其用途(例如,一个带有“开始”标题的标签,或一个带有“文件夹”图标的标签,用于指示“打开文件操作”),并能够响应某些事件,例如触摸或鼠标单击。

Button 类在“kivy.uix.button”模块中定义。Button 对象的外观可以通过在 Label 类中定义的同一组属性进行配置。Button 类也继承自 ButtonBehavior 混合。

Button 对象使用以下语法实例化:

b1 = Button(**kwargs)

要配置按钮,您可以将它的属性指定为构造函数的关键字参数:

  • background_color − 按钮的背景颜色是一个 ColorProperty,格式为 (r, g, b, a),默认值为 [1,1,1,1]。

  • background_disabled_down − 按钮的背景图像是 StringProperty,一个包含图像文件路径的字符串,用于按钮禁用并按下时的默认图形表示。

  • background_disabled_normal − 按钮的背景图像也是一个图像路径,用于按钮禁用且未按下时的默认图形表示。

  • background_down − 按钮的背景图像,用作按钮按下时的默认图形表示。

  • background_normal − 按钮的背景图像,用作按钮未按下时的默认图形表示。

除了以上内容外,Button 按钮还继承了 Label 标签类的属性,其中一些如下所示:

  • bold − 指示是否使用粗体字体的版本。它是一个 BooleanProperty 属性,默认为 False。

  • underline − 为文本添加下划线。此功能需要 SDL2 文本提供程序,它是一个 BooleanProperty 属性,默认为 False。

  • strikethrough − 为文本添加删除线。此功能需要 SDL2 文本提供程序。它是一个 BooleanProperty 属性,默认为 False。

  • text − 标签的文本。例如:

widget = Button(text='Hello world')

text 是一个 StringProperty 属性,默认为 ''。

  • color − 文本颜色,格式为 (r, g, b, a)。它是一个 ColorProperty 属性,默认为 [1, 1, 1, 1]。

  • font_size − 文本的字体大小,以像素为单位。“font_size” 是一个 NumericProperty 属性,默认为 15sp。

Button 类还继承了 ButtonBehavior 类的 state 属性。

  • state − 按钮的状态,必须是 'normal' 或 'down' 之一。当按钮当前被触摸/点击时,状态为 'down',否则为 'normal'。它是一个 OptionProperty 属性,默认为 'normal'。

Button 类还继承了 Widget 类的属性,例如 disabled、height、widthpos 等。

如果要在 Kivy 应用程序窗口中显示一个 Button 按钮,则可以通过在 build() 方法中声明一个 Button 对象来实现,或者使用 "kv" 语言脚本。

使用 build() 方法显示 Button 按钮

让我们使用上面解释的一些属性来配置 Button 按钮:

示例

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class HelloApp(App):
   def build(self):
      b1 = Button(text = "A Kivy Button",
         font_size=50,
         color = [0.8, 0.2, 0.3, 1])
      return b1

app = HelloApp()
app.run()

输出

运行此代码,您将获得以下输出

Kivy Button1

使用 "kv" 语言方法显示 Button 按钮

示例

将以下脚本保存为 "hello.kv"。

Button:
   text: 'A Kivy Button'
   font_size: '20pt'
   underline: True
   background_color: [1,0,0,1]
   size_hint: (.25, .25)
   pos_hint: {'center_x':.5, 'center_y':.5}

输出

注释掉 App 类中的 build() 方法,然后再次运行应用程序。您将获得以下窗口作为输出

kivy button2

Kivy - 按钮事件

Button 按钮(与 Kivy 中的大多数 GUI 小部件一样)被编程为响应特定类型的事件。Button 按钮处理以下事件类型:

  • **on_press** - 当按钮被按下时触发。

  • **on_release** - 当按钮被释放时触发。

  • **on_touch_down** - 当按钮上开始触摸事件时触发。

  • **on_touch_up** - 当按钮上结束触摸事件时触发。

Kivy 的 EventDispatcher 类提供了一个 bind() 方法,该方法负责将事件委派给某个回调函数进行处理。

EventDispatcher.bind(self, **kwargs)

Button 按钮(每个 Kivy 小部件也一样)继承了此方法。因此,我们可以将 Button 对象绑定到任何回调事件处理函数。您还可以将属性绑定到回调函数。

绑定事件

下面是绑定按钮的 on_press 事件到函数的典型方法:

def callback(instance):
   print('The button is being pressed')
   
btn1 = Button(text='Hello world')
btn1.bind(on_press=callback)

示例

在下面的示例中,我们在 FloatLayout 中放置了两个按钮。每个按钮的 "on_press" 事件都绑定到 callback() 方法。

发生 "on_press" 事件的按钮的引用被传递给 callback() 方法,以便我们可以识别按下按钮的标题。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class ButtonApp(App):
   def on_button_press(self, instance):
      print("{} Button pressed!".format(instance.text))

   def build(self):
      flo = FloatLayout()
      btn1 = Button(text= 'Hello World',
         background_color= [1,0,0,1],
         font_size= 20, underline= True,
         size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.8})
      btn1.bind(on_press = self.on_button_press)

      btn2 = Button(text= 'Hello Python',
         color= [0,0,1,1], font_size= 20,
         size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.2})
      flo.add_widget(btn1)
      btn2.bind(on_press = self.on_button_press)
      flo.add_widget(btn2)
      return flo

if __name__ == '__main__':
   ButtonApp().run()

输出

运行以上代码并按下按钮:

Kivy Button Events1

每次按下时,都会调用 callback() 方法:

Hello World Button pressed!
Hello Python Button pressed!

绑定属性

如前所述,我们可以将回调函数绑定到小部件的属性。每次属性的值发生变化时,都会调用回调函数以通知更改。

btn1.bind(property=callback)

让我们在 App 类中定义另一个方法 "on_textchanged()",并将其与 btn2 的 text 属性绑定。btn1 上的 on_press 事件会更改 btn2 的标题,并且更改会立即调用 on_textchanged() 方法。

示例

ButtonApp 类的代码更改如下:

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class ButtonApp(App):
   def on_button_press(self, instance):
      print("{} Button pressed!".format(instance.text))
      self.btn2.text="Hello Tutorialspoint"

   def on_textchanged(self, instance, value):
      print ("Text property changed to", instance.text)

   def build(self):
      flo = FloatLayout()
      self.btn1 = Button(text= 'Hello World',
         background_color= [1,0,0,1],
         font_size= 20, underline= True,
         size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.8})

      self.btn1.bind(on_press = self.on_button_press)
      self.btn2 = Button(text= 'Hello Python', color= [0,0,1,1],
         font_size= 20, size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.2})
      flo.add_widget(self.btn1)
      self.btn2.bind(text = self.on_textchanged)
      flo.add_widget(self.btn2)
      return flo

if __name__ == '__main__':
   ButtonApp().run()

输出

运行代码并首先按下btn1。它会更改btn2的标题,进而调用 "on_textchanged()" 方法。

Hello World Button pressed!
Text property changed to Hello Tutorialspoint

以下是输出窗口:

Kivy Button Events2

通常,属性回调函数使用两个参数(对象和属性的新值)调用,而“事件回调函数”使用一个参数(对象)调用。

使用 Lambda 函数绑定

另一种绑定方法是使用 lambda(或匿名)函数。它们的优点是可以避免声明新函数,即它们提供了一种简洁的“重定向”回调函数的方法。

将绑定 btn1 的 "on_press" 事件的语句更改为:

self.btn1.bind(on_press = lambda btn1: self.on_button_press(btn1))

使用 Partial 函数

在 Python 中,Partial 函数允许我们将具有 x 个参数的函数派生为具有较少参数的函数,并为更有限的函数设置常量值。它使函数可重用。partial() 函数在 Python 标准库的 functools 模块中定义。

示例

我们可以将事件绑定到部分方法。在下面的示例中,传递了 Button 对象 bt1btn2。该函数交换这两个对象的 text 属性。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout
from functools import partial

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class ButtonApp(App):
   def on_textchanged(self, instance, value):
      print ("Text property changed to", instance.text)

   def a_function(self, *args):
      args[0].text, args[1].text = args[1].text, args[0].text

   def build(self):
      flo = FloatLayout()

      self.btn1 = Button(text= 'Hello World',
         background_color= [1,0,0,1],
         font_size= 20, underline= True,
         size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.8})
         
      self.btn2 = Button(text= 'Hello Python',
         color= [0,0,1,1],
         font_size= 20,
         size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.2})
      flo.add_widget(self.btn1)
      self.btn1.bind(on_press = partial(self.a_function, self.btn1, self.btn2))
      self.btn2.bind(text = self.on_textchanged)
      flo.add_widget(self.btn2)
      return flo

if __name__ == '__main__':
   ButtonApp().run()

输出

请查看以下输出窗口,并观察按下第一个按钮如何交换两个按钮的文本:

Button Hello Word

Kivy - 按钮颜色

在任何 GUI 应用程序中,按钮都是一个重要的组件。其主要功能是响应点击事件并调用回调函数。为了设计吸引人的 GUI,应适当地选择按钮颜色。您可以通过指定其标题的颜色、正常状态和禁用状态下的背景颜色来配置按钮。

在 Kivy 中,Button 类定义了以下与颜色相关的属性:

  • color
  • background_color
  • disabled_color
  • outline_color
  • disabled_outline_color

color 属性

Button 类从 Label 类继承了此属性,因为 Button 是一个响应点击相关事件的 Label。color 属性定义按钮文本或按钮标题的颜色。

由于 color 是 ColorProperty 类型,因此必须以 (r,g,b,a) 格式指定。颜色取值为“0”到“1”之间。“a”组件用于透明度。对于按钮,color 默认为 [1, 1, 1, 1]。

background_color 属性

它充当纹理颜色的乘数。默认纹理为灰色,因此仅设置背景颜色将产生较暗的结果。按钮的背景颜色是一个 ColorProperty,格式为 (r, g, b, a),默认值为 [1,1,1,1]。

disabled_color 属性

此属性继承自 Label 类。它定义按钮禁用时按钮文本或标题的颜色。它是一个 ColorProperty,默认为 [1,1,1,3]。

outline_color 属性

继承自 Label 类,此属性配置文本轮廓的颜色。请注意,这需要 SDL2 文本提供程序。此属性为 ColorProperty 类型,其默认值为 [0,0,0,1]。

disabled_outline_color 属性

此属性定义小部件禁用时文本轮廓的颜色,格式为 (r, g, b)。它继承自 Label 类。此功能需要 SDL2 文本提供程序。disabled_outline_color 是一个 ColorProperty,默认为 [0, 0, 0]。

示例 1

让我们演示 color 和 disabled_color 属性的使用。在下面的示例中,我们在 floatlayout 中放置了两个按钮。它们使用不同的 color 和 disabled_color 属性实例化。单击时,文本颜色会发生变化。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class HelloApp(App):
   def on_button_press(self, instance):
      instance.disabled = True

   def build(self):
      flo = FloatLayout()

      btn1 = Button(text= 'Hello Python', color= [1,0,0,1],
         disabled_color = [0,0,1,1],
         font_size= 40, size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.8})
      btn1.bind(on_press = self.on_button_press)
      btn2 = Button(text= 'Hello Kivy', color= [0,0,1,1],
         disabled_color = [1,0,0,1],
         font_size= 40, size_hint= (.4, .25),
         pos_hint= {'center_x':.5, 'center_y':.2})
      flo.add_widget(btn1)
      btn2.bind(on_press = self.on_button_press)
      flo.add_widget(btn2)
      return flo

if __name__ == '__main__':
   HelloApp().run()

输出

最初,这两个按钮都处于启用状态。当您按下按钮时,它们会变为禁用状态(它们无法接收“on_press”事件),并且文本颜色会根据配置更改。

Kivy Button Colors

示例 2

在下面的程序中,当单击任何按钮时,其文本颜色和背景颜色会互换。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class HelloApp(App):
   def on_button_press(self, instance):
      print("change color")
      instance.background_color, instance.color = instance.color, instance.background_color

   def build(self):
      flo = FloatLayout()

      self.btn1 = Button(text='Hello Python',
         color=[1, 0, 0, 1],
         background_color=[0, 0, 1, 1],
         font_size=40, size_hint=(.4, .25),
         pos_hint={'center_x': .5, 'center_y': .8})

      self.btn2 = Button(text='Hello Kivy',
         color=[0, 0, 1, 1],
         background_color=[1, 0, 0, 1],
         font_size=40, size_hint=(.4, .25),
         pos_hint={'center_x': .5, 'center_y': .2})
      flo.add_widget(self.btn1)
      self.btn1.bind(on_press=self.on_button_press)
      self.btn2.bind(on_press=self.on_button_press)
      flo.add_widget(self.btn2)
      return flo

if __name__ == '__main__':
   HelloApp().run()

输出

当您单击任意两个按钮时,它们的色彩会像此处显示的那样互换:

Kivy Button Colors Hello

Kivy - 按钮大小

Kivy 应用程序用户界面上的小部件应具有适当的大小非常重要。与 position 属性一样,按钮的大小属性(任何小部件都一样)受其所在布局的控制。

可以通过“size”和“size_hint”这两个属性来配置按钮的大小。“kivy.uix.button.Button”类从 Widget 类继承了这些属性。

按钮的“size_hint”属性是一个值元组,其父布局使用该元组来决定大小。它定义相对于布局大小的大小,而不是绝对大小。例如:

btn.size_hint = (w, h)

两个参数“w”和“h”都指定为 0 到 1 范围内的浮点数。例如,0.5 表示 50%,1 表示 100%。

# This button has width and height of the parent layout
btn.size_hint=(1,1)

# Width of this button will be half of the container's width
btn.size_hint=(0.5, 1)

# This button will be of width and height 20% of the layout
btn.size_hint=(.2,.2)

另一方面,“size”属性以绝对术语分配按钮的宽度和高度,并以像素为单位表示。

btn.size=(200,100)

但是,要使按钮具有绝对大小,您必须要求 Kivy 布局忽略大小提示。如果您不想对宽度或高度使用 size_hint,请将该值设置为 None。换句话说,您必须在以绝对测量单位分配大小之前设置“size_hint=(None, None)”。

您还可以使用“size_hint_x”和“size_hint_y”属性分别设置宽度或高度的大小提示。

假设您想制作一个宽度为 250px 且高度为父元素高度 30% 的按钮。

btn.size_hint_x = None
btn.size_hint_y= 0.3
widget.width = 250

这些属性也可以在 Button 构造函数参数中设置:

btn = Button(text="Hi there!", size_hint=(None, 0.3), width=250)

示例

以下程序将各种按钮放置在应用程序窗口的 FloatLayout 中,并使用 size_hint、size、pos_hint 和 pos 属性的不同组合:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.core.window import Window

Window.size = (720,400)

class DemoApp(App):
   def build(self):
      f = FloatLayout()

      b1 = Button(text="B1", size_hint=(None, None))
      f.add_widget(b1)

      b2 = Button(text="B2", size_hint=(1, None), height=20)
      f.add_widget(b2)

      b3 = Button(text="B3", size_hint=(None, None), pos=(0, 100), size=(400, 100))
      f.add_widget(b3)
      b4 = Button(text='B4', size_hint=(None,.3), width=50, pos_hint={'x':.6, 'y':.2} )
      f.add_widget(b4)

      b5 = Button(text='B5', size_hint=(None,.9), width=50, pos_hint={'x':.5, 'y':.5} )
      f.add_widget(b5)
      return f

if __name__ == '__main__':
   DemoApp().run()

输出

运行此代码后,您将获得以下输出窗口:

Kivy Button Size

Kivy - 按钮位置

将小部件放置在适当的位置是设计符合人体工程学用户界面的关键。在 Kivy 中,按钮(以及其他小部件)的位置主要由使用的布局控制。在本章中,我们将学习如何在 Kivy 应用程序窗口的某个位置放置按钮。

决定位置的第一个因素是布局。在 Kivy 中,布局是用于以特定方式排列小部件的容器。例如:

  • BoxLyout 按顺序放置小部件,可以是垂直顺序或水平顺序。

  • 如果使用 GridLayout,则小部件的位置由 rows 和 cols 属性决定。

  • FloatLayout 对放置没有限制。您可以通过分配按钮或任何其他小部件的绝对坐标将其放置在任何位置。

窗口大小

要在某个位置放置按钮,我们首先定义应用程序窗口的大小。Window 对象的“size”属性可帮助您设置所需的大小。

from kivy.core.window import Window
Window.size = (720,400)

Kivy 的窗口坐标系定义小部件的位置以及分派给它们触摸事件。它将 (0, 0) 放置在窗口的左下角。显然,窗口的右上角对应于 (1,1)。

Button 类从 Widget 类继承了“pos”和“pos_hint”属性。它们有助于确定按钮在窗口表面上的位置。

位置属性

pos − 此属性是水平和垂直轴上坐标值“x”和“y”的元组,从窗口的左下角测量。例如:

button = Button(text ='Hello world', pos =(20, 20))

pos_hint − 此属性提供小部件位置的提示。它允许您在父布局内设置小部件的位置。该属性是一个最多包含 8 个键的字典,用于确定位置:

  • x
  • y
  • left
  • right
  • top
  • bottom
  • center_x
  • center_y

键“x”、“right”和“center_x”将使用父级宽度。键“y”、“top”和“center_y”将使用父级高度。例如,如果要将按钮的顶部设置为其父布局高度的 10%,则可以编写:

button = Button(text ='Hello world', pos_hint={'top': 0.1})

"pos_hint" 是一个 ObjectProperty。并非所有布局都使用它。

支持定位的布局

  • FloatLayout − 支持 "pos_hint" 属性。这些值是 0 到 1 之间的数字,表示窗口大小的比例。

  • RelativeLayout − 定位属性(pos、x、center_x、right、y、center_y 和 top)相对于布局大小,而不是窗口大小。

  • BoxLayout − 仅在垂直方向上 "x" 键(x、center_x 和 right)有效,在水平方向上 "y" 键(y、center_y、top)有效。固定定位属性(pos、x、center_x、right、y、center_y 和 top)也遵循相同的规则。

在下面的代码中,我们在上方的垂直 BoxLayout 中放置了一个水平 BoxLayout 和一个 FloatLayout。上方的水平框包含四个按钮:LEFT、RIGHT、TOP 和 BOTTOM。在 FloatLayout 内部,我们使用 "pos" 属性放置了一个按钮。

App 类有一个名为 movebtn() 的方法,它识别按下按钮的标题并更改按钮的 "x" 或 "y" 位置。

def movebtn(self,instance):
   if instance.text =='RIGHT':
      self.btn.pos[0]=self.btn.pos[0]+10
   
   if instance.text == 'LEFT':
      self.btn.pos[0] = self.btn.pos[0]-10
   
   if instance.text == 'UP':
      self.btn.pos[1] = self.btn.pos[1]+10
   
   if instance.text == 'DOWN':
      self.btn.pos[1] = self.btn.pos[1]-10

RIGHT 和 LEFT 按钮按下会导致 "x" 位置增加或减少 10 像素。类似地,TOP 和 BOTTOM 按钮将 "y" 值更改 ±10。

示例 1

完整代码如下所示:

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.boxlayout import BoxLayout

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class MovableButtonApp(App):
   def movebtn(self,instance):
      if instance.text =='RIGHT':
         self.btn.pos[0]=self.btn.pos[0]+10

      if instance.text == 'LEFT':
         self.btn.pos[0] = self.btn.pos[0]-10

      if instance.text == 'UP':
         self.btn.pos[1] = self.btn.pos[1]+10

      if instance.text == 'DOWN':
         self.btn.pos[1] = self.btn.pos[1]-10

   def build(self):
      mblo = BoxLayout(orientation='vertical')
      blo = BoxLayout(orientation ='horizontal')
      b1 = Button(text='LEFT')
      b1.bind(on_press=self.movebtn)
      b2 = Button(text = 'RIGHT')
      b2.bind(on_press=self.movebtn)
      b3 = Button(text = 'UP')
      b3.bind(on_press=self.movebtn)
      b4 = Button(text = 'DOWN')
      b4.bind(on_press=self.movebtn)
      blo.add_widget(b1)
      blo.add_widget(b2)
      blo.add_widget(b3)
      blo.add_widget(b4)

      mblo.add_widget(blo)
      flo = FloatLayout()
      self.btn = Button(text='Movable Button', size_hint= (.350, .150))
      flo.add_widget(self.btn)
      mblo.add_widget(flo)
      return mblo
MovableButtonApp().run()

输出

运行程序时,您应该会看到顶部有四个按钮,左下角有一个可移动的按钮。按下按钮,查看可移动按钮的位置变化。

Kivy Button Position

这是另一个演示按钮定位用法的示例。让我们定义一个扩展 Button 类的 MovableButton 类。我们定义 on_touch_down()、on_touch_up() 和 on_touch_move() 方法来处理触摸事件。

on_touch_down() 方法检查触摸事件是否发生在按钮的边界内,通过将小部件设置为当前触摸目标来处理触摸事件。

def on_touch_down(self, touch):
   if self.collide_point(*touch.pos):
      touch.grab(self)
      return True
   return super().on_touch_down(touch)

如果我们的按钮正在处理触摸事件,则使用 on_button_move() 方法更新其位置:

def on_touch_move(self, touch):
   if touch.grab_current == self:
      self.pos = (self.pos[0] + touch.dx, self.pos[1] + touch.dy)

最后,释放按钮作为当前触摸目标并处理触摸事件。

def on_touch_up(self, touch):
   if touch.grab_current == self:
      touch.ungrab(self)
      return True
   return super().on_touch_up(touch)

build() 方法构建窗口,窗口中只有一个位于左下角的按钮。

def build(self):
   return MovableButton(text='Drag me', size_hint= (.250, .100))

示例 2

完整代码如下所示:

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.floatlayout import FloatLayout

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class MovableButton(Button):
   def on_touch_down(self, touch):
      if self.collide_point(*touch.pos):
         touch.grab(self)
         return True
      return super().on_touch_down(touch)

   def on_touch_move(self, touch):
      if touch.grab_current == self:
         self.pos = (self.pos[0] + touch.dx, self.pos[1] + touch.dy)

   # Override the on_touch_up method to update
   # the widget's position when the touch event ends
   def on_touch_up(self, touch):
      if touch.grab_current == self:
         touch.ungrab(self)
         return True
      return super().on_touch_up(touch)

class TestApp(App):
   def build(self):
      return MovableButton(text='Drag me', size_hint=(.250, .100))

if __name__ == "__main__":
   TestApp().run()

输出

运行以上代码。按住鼠标按钮并拖动按钮到窗口的任意位置。

Kivy Button Position Move

Kivy - 圆形按钮

Kivy 框架中的所有小部件都是矩形形状。按钮对象始终具有直角。因此,创建圆角按钮没有简单的解决方案,但是我们可以通过一些技巧来实现它。

使用图像作为按钮

我们可以定义一个扩展 ButtonBehavior 混合和 Image 类的类。使用任何照片编辑器,创建一个看起来像圆形按钮的椭圆形,并将其用作 Image 对象的 source 属性。

您可以覆盖 ButtonBehavior 类的 on_press() 方法,使图像可以用作按钮。

from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
      self.source = 'hello.png'
      self.pos_hint= {'center_x':.5, 'center_y':.6}

   def on_press(self):
      print("Button pressed")

我们现在可以在 Kivy App 中使用 imgbtn 对象。

KivyMD 按钮

使用 KivyMD 扩展,我们可以设计更具吸引力的界面。KivyMD 是一个材料设计小部件的集合,用于 Kivy 应用程序。KivyMD 库提供了带有圆角的不同按钮对象。

  • MDRoundFlatButton
  • MDRoundFlatIconButton
  • MDFillRoundFlatButton
  • MDFillRoundFlatIconButton

首先,安装 KivyMD 扩展(确保 Kivy 框架已提前安装)。

pip3 install KivyMD

App 类必须是 MDApp 类的子类,而不是 App 类。在这个例子中,我们将使用 MDRoundFlatButton 类。它的大多数属性与 Kivy Button 相同。

from kivymd.app import MDApp
from kivymd.uix.button import MDRoundFlatButton

btn = MDRoundFlatButton(
   text= 'Hello Python',
   font_size= 20,
   size_hint= (.3, .1),
   pos_hint= {'center_x':.5, 'center_y':.3},
   line_width=3
)

示例

在下面的示例中,我们有一个 MDApp 类。build() 方法将一个图像按钮和一个 MDRoundButton 对象放在应用程序窗口中。

from kivymd.app import MDApp
from kivy.core.window import Window
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivymd.uix.button import MDRoundFlatButton

Window.size = (720, 300)

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
      self.source = 'btnnormal.png'
      self.pos_hint= {'center_x':.5, 'center_y':.6}

   def on_press(self):
      print("Button pressed")

class ButtonApp(MDApp):
   def build(self):
      flo = FloatLayout()
      self.btn1 = imgbtn()
      self.btn2 = MDRoundFlatButton(
         text= 'Hello Python',
         font_size= 20, size_hint= (.3, .1),
         pos_hint= {'center_x':.5, 'center_y':.3},
         line_width=3
      )
      flo.add_widget(self.btn1)
      flo.add_widget(self.btn2)
      return flo

if __name__ == '__main__':
   ButtonApp().run()

输出

运行应用程序。您应该会得到以下输出,其中包含圆角按钮。

Kivy Round Button

使用 Canvas

在 Kivy 中,canvas 是小部件绘制的根对象。为了模拟 Label 作为圆形按钮工作,我们定义了一个扩展 ButtonBehavior 和 Label 的类。 "kv" 文件定义了此对象的结构,如下所示:

<RoundCorneredButton>:
   canvas:
      Color:
         rgb: (1, 0, 0, 1) if self.state == 'normal' else (0, 0, 0, 1)
      RoundedRectangle:
         size: (self.size)
         pos: (self.pos)
         radius: [200, ]
   on_release:
      print("This is the button made up by the canvas")

类的定义如下所示:

class RoundCorneredButton(ButtonBehavior, Label):
   pass

示例

我们将在以下 App 代码中使用上述类和 kv 设计:

from kivy.app import App
from kivy.uix.label import Label
from kivy.config import Config
from kivy.uix.button import ButtonBehavior
from kivy.graphics import Rectangle, Color

# Configuration
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

from kivy.app import App
class RoundCorneredButton(ButtonBehavior, Label):
   pass

class HelloApp(App):
   def build(self):
      return RoundCorneredButton()

HelloApp().run()

输出

现在运行代码。您将获得一个圆形形状的按钮,如下所示:

Kivy Button Circular Shape

Kivy - 禁用按钮

Kivy API 具有不同类型的按钮小部件。Button、ToggleButton、CheckBox 的对象都是具有不同特征的按钮。它们都有一个共同的属性。它们可以接受和传播点击事件上的“触摸”事件。所有按钮对象都可以引发按钮事件,因为这些类继承了 ButtonBehavior 接口。

您可以通过将 "disabled" 属性设置为 True 来使按钮对按钮事件无响应。(disabled 属性的默认值为 False。disabled 属性继承自 Widget 类。)

from kivy.uix.button import Button
b1 = Button(text="OK", disabled=True)

为了从正常或启用按钮配置禁用按钮的外观,可以使用以下属性:

  • background_disabled_down − 按钮的背景图像是 StringProperty,一个包含图像文件路径的字符串,用于按钮禁用并按下时的默认图形表示。

  • background_disabled_normal − 按钮的背景图像也是一个图像路径,用于按钮禁用且未按下时的默认图形表示。

  • disabled_color − 此属性继承自 Label 类。它定义了按钮文本或标题在禁用时的颜色。它是一个 ColorProperty,默认为 [1,1,1,3]。

  • disabled_outline_color − 此属性定义了小部件禁用时文本轮廓的颜色,格式为 (r, g, b)。它继承自 Label 类。此功能需要 SDL2 文本提供程序。disabled_outline_color 是一个 ColorProperty,默认为 [0, 0, 0]。

示例

下面给出的代码在垂直 BoxLayout 中排列了一个 ToggleButton 和一个普通 Button。如果 ToggleButton 的状态为按下或正常,则它会将另一个按钮的 disabled 属性更改为 True 或 False。

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.togglebutton import ToggleButton
from kivy.core.window import Window
from kivy.uix.boxlayout import BoxLayout

Window.size = (720, 350)

class HelloApp(App):
   def on_button_press(self, instance, value):
      if value == 'down':
         self.btn2.disabled = True
         self.btn2.text = 'Disabled'
         instance.text = 'Enable Btn 2'
      if value == 'normal':
         self.btn2.disabled = False
         self.btn2.text = 'Enabled'
         instance.text = 'Disable Btn 2'

   def build(self):
      flo = BoxLayout(orientation='vertical')

      btn1 = ToggleButton(
         text='Disable Btn 2', font_size=40,
         size_hint=(1, .25),
         pos_hint={'center_x': .5, 'center_y': .8}
      )
      btn1.bind(state=self.on_button_press)
      self.btn2 = Button(
         text='Enabled', color=[0, 0, 1, 1],
         disabled_color=[1, 0, 0, 1], font_size=40,
         size_hint=(1, .25),
         pos_hint={'center_x': .5, 'center_y': .2}
      )
      flo.add_widget(btn1)
      flo.add_widget(self.btn2)
      return flo

if __name__ == '__main__':
   HelloApp().run()

输出

程序运行时,它会显示底部的按钮(即 btn2)为启用状态,其标题为 Color 属性。

Kivy Disabled Buttons

当顶部的按钮 (btn1) 被按下时,下面的按钮将被禁用,颜色更改为 "disabled_color" 属性指定的颜色。

Kivy - 图片按钮

Kivy 库没有现成的图像按钮小部件。它确实有一个普通的 Button 和一个 ToggleButton 小部件。您当然可以在正常状态或禁用状态下使用图像文件作为其背景:

  • background_disabled_down − 按钮的背景图像是 StringProperty,一个包含图像文件路径的字符串,用于按钮禁用并按下时的默认图形表示。

  • background_disabled_normal − 按钮的背景图像也是一个图像路径,用于按钮禁用且未按下时的默认图形表示。

  • background_down − 按钮的背景图像,用作按钮按下时的默认图形表示。

  • background_normal − 按钮的背景图像,用作按钮未按下时的默认图形表示。

例如,您可以使用:

B1 = Button(background_normal='images/play.png')

但是,要使图像小部件充当可点击按钮,您需要根据 ButtonBehavior 混合和 Image 类定义一个自定义类,并覆盖 on_press() 方法。

ButtonBehavior

在 Kivy 中,"kivy.uix.behaviors" 模块定义了行为混合,也称为“可重用类”,它们为小部件提供了额外的功能。

要将图像用作按钮,我们定义一个扩展 ButtonBehavior 的自定义类,使其能够响应 on_press 或 on_touch 等事件,以便图像本身可以充当按钮。

我们使用 Kivy 的 Image 对象在 Kivy 窗口上显示图像。但是,要向其添加类似按钮的行为,我们首先定义一个名为 "imgbtn" 的自定义类,该类扩展了 Image 和 ButtonBehavior 类。

Image 类的 source 属性被分配一个字符串,该字符串是图像文件的路径。然后我们覆盖 on_press() 方法。

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
   def on_press(self):
      print("Button pressed", self.source)
      ImgBtnApp.l1.text=self.source

示例

让我们实现这个概念,并在应用程序布局中放置四个图像并将它们绑定到回调。提供类似按钮的功能到图像的类首先定义如下:

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)
   def on_press(self):
      ImgBtnApp.l1.text=self.source

我们现在将在应用程序布局上使用此类的对象来显示图像,并且它们将引发 on_press 事件。被点击图像的 source 属性将显示在 Label 上作为其文本。

from kivy.app import App
from kivy.graphics import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.behaviors import ButtonBehavior
from kivy.core.window import Window

Window.size = (720, 400)

class imgbtn(ButtonBehavior, Image):
   def __init__(self, **kwargs):
      super(imgbtn, self).__init__(**kwargs)

   def on_press(self):
      print("Button pressed", self.source)
      ImgBtnApp.l1.text = self.source

class ImgBtnApp(App):
   def build(self):
      main = GridLayout(cols=1)
      ImgBtnApp.l1 = Label(text='Hello', font_size=32)
      main.add_widget(ImgBtnApp.l1)
      root = FloatLayout(size=(Window.width, 100))
      with root.canvas:
         Color(.2, .7, .1, 1)
         Rectangle(pos=root.pos, size=root.size)
      
      self.btn1 = imgbtn(
         source='previous.png', size_hint=(None, None),
         pos_hint={'center_x': .2, 'center_y': .25}
      )
      self.btn2 = imgbtn(
         source='play.png', size_hint=(None, None),
         pos_hint={'center_x': .4, 'center_y': .25}
      )
      self.btn3 = imgbtn(
         source='pause.png', size_hint=(None, None),
         pos_hint={'center_x': .6, 'center_y': .25}
      )
      self.btn4 = imgbtn(
         source='stop.png', size_hint=(None, None),
         pos_hint={'center_x': .8, 'center_y': .25}
      )
      root.add_widget(self.btn1)
      root.add_widget(self.btn2)
      root.add_widget(self.btn3)
      root.add_widget(self.btn4)
      
      main.add_widget(root)
      return main
      
ImgBtnApp().run()

输出

运行代码并依次点击四个按钮中的每一个。

Kivy Image Button

Kivy - 控件

Kivy 应用程序的用户界面使用 Kivy 库中的各种小部件进行设计。"kivy.uix" 模块包含对应于小部件名称的类的定义。这些类提供了对应小部件对象的属性和功能。

Kivy 库中的各种小部件可以归类为以下类别:

通用小部件

从本质上讲,这些小部件是经典的,因为它们用于大多数应用程序的界面设计。UX 小部件(如 Label、各种 Button 类型、输入框、Image 容器、滑块和进度指示器等)属于此类别。

下面显示了一些 UX 小部件:

Kivy Widgets

布局

Kivy 应用程序窗口只能包含一个小部件作为其根对象。但是,如果您需要使用多个控件来组合应用程序界面,则必须使用布局小部件,并将多个 UX 小部件放置在其中,然后将布局作为根小部件放在应用程序窗口上。

需要注意的是,布局小部件本身没有视觉表示。Kivy 提供了各种布局,例如网格布局、框布局、浮动布局等。

复杂 UX 小部件

此类型的小部件是组合多个经典小部件的结果。它们很复杂,因为它们的组装和使用不像经典小部件那样通用。

复杂小部件类别的示例包括下拉列表、文件选择器、微调器、视频播放器、虚拟键盘等。

行为小部件

这些小部件没有自己的渲染,但会响应其子级的图形指令或交互(触摸)行为。Scatter、Stencil View 小部件属于此类型。

屏幕管理器

管理屏幕并在从一个屏幕切换到另一个屏幕时进行过渡。

Widget 类

Widget 类(定义在 kivy.uix.widget 模块中)是所有小部件类的基础。此 Widget 类中定义的常见属性(如 size、width、height、pos、pos_hint 等)被其他小部件继承。

任何 Widget 对象的交互性取决于两个方面:“事件处理程序”和“属性回调”。如果一个小部件绑定到某个处理程序以在发生特定类型的事件时发生,则会调用相应的处理程序函数。

def callback(instance):
   print('Callback handler')

wid = Widget()
wid.bind(on_event=callback)

回调函数也基于某个属性被调用。如果属性发生更改,则小部件可以通过 'on_<propname>' 回调响应更改。

def on_pos_change(instance, value):
   print('The widget position changed to ', value)

wid = Widget()
wid.bind(pos=on_pos_change)

基础 Widget 类或任何小部件都没有 draw() 方法。每个小部件都有自己的 Canvas,您可以使用它进行绘制。

widget = Widget()
with widget.canvas:
   Rectangle(pos=widget.pos, size=widget.size)

Color、Rectangle 和 Line、Scale 和 Rotate 等图形指令可以添加到任何小部件的 Canvas 中。

在 Kivy 中,事件从第一个子级向上传播到其他子级。如果一个小部件有子级,则事件会在传递到其后面的小部件之前先通过其子级传递。

每个小部件都通过 add_widget() 方法添加到其父级。每次添加都由一个递增的索引标识。

box = BoxLayout()
l1=Label(text="a")
l1=Label(text="b")
l1=Label(text="c")
box.add_widget(l1)
box.add_widget(l2)
box.add_widget(l3)

分别为 l1、l2、l3 添加标签的索引为 0、1 和 2。您可以显式地将 index 参数传递给 add_widget() 方法。

box.add_widget(l1, index=1)
box.add_widget(l2, index=0)
box.add_widget(l3, index=2)

如果小部件排列是嵌套的,则最内部小部件上的事件会向上传播。假设 Button 是一个子级小部件,添加到 Widget 对象本身。因此,touch_down 回调将同时为这两个对象调用。要确认触摸事件仅发生在按钮上,请使用 collide_points() 方法。

def callback(self, touch):
   if instance.collide_point(touch.x, touch.y):
      #consume event
      return True
   else:
      #the upper level widget processes the event

示例

一个基本的 Kivy 应用程序在使用以下代码添加到 Widget 对象的 Label 上显示一条简单的单行消息:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.core.window import Window

Window.size = (720,300)

class HelloApp(App):
   def build(self):
      w = Widget()
      l1=Label(
         text='Fortune Favours the Brave!',
         font_size=50, size=(200,100),
         pos_hint={'center_x':.5, 'center_y':.5},
         pos=(250,200)
      )
      w.add_widget(l1)
      return w

app = HelloApp()
app.run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Widgets Window

Kivy - 标签

标签是任何 GUI 工具包中最常使用的小部件之一。标签显示任何无法直接编辑的文本内容。标签用于显示页面标题,作为字段名称的占位符以及文本框等输入控件,或仅用于呈现输出消息。

在 Kivy 中,标签小部件是 Label 类的一个对象,定义在 "kivy.uix.label" 模块中。

from kivy.uix.Label import Label
lbl = Label(**kwargs)

要自定义标签对象,您可以将以下属性用作构造函数的关键字参数:

  • bold − bold 是一个 BooleanProperty,默认为 False。将其设置为 True 以使用字体粗体版本。请注意,根据您的字体,粗体属性可能对您的文本渲染没有影响。

  • color − 文本颜色,格式为 (r, g, b, a)。它是一个 ColorProperty,默认为 [1, 1, 1, 1]。

  • disabled_color − 标签禁用时的文本颜色,格式为 (r, g, b, a)。它是一个 ColorProperty,默认为 [1, 1, 1, .3]。

  • font_name − 要使用的字体文件名。font_name 是一个 StringProperty,默认为 'Roboto'。此值取自 Config。

  • font_size − 文本的字体大小,单位为像素。它是一个 NumericProperty,默认为 15sp。

  • halign − 文本的水平对齐方式。halign 是一个 OptionProperty,默认为 'auto'。可用选项包括:auto、left、center、right 和 justify。

  • italic − 指示是否使用字体的斜体版本。italic 是一个 BooleanProperty,默认为 False。

  • markup − 如果为 True,则文本将使用 MarkupLabel 渲染:您可以使用标签更改文本的样式。

  • outline_color − 文本轮廓的颜色,格式为 (r, g, b)。它是一个 ColorProperty,默认为 [0, 0, 0, 1]

  • padding − 文本的内边距,格式为 [padding_left, padding_top, padding_right, padding_bottom]。padding 也接受两个参数的形式 [padding_horizontal, padding_vertical] 和一个参数的形式 [padding]。

  • strikethrough − 为文本添加删除线。strikethrough 是一个 BooleanProperty,默认为 False。

  • text − 标签标题的文本。text 是一个 StringProperty,默认为 ''。例如 -

lbl = Label(text='Hello world')
  • text_size − 默认情况下,标签不受任何边界框的约束。您可以使用此属性设置标签的大小约束。文本将自动流入约束。因此,虽然字体大小不会减小,但文本将尽可能地排列以适合框中,框外的任何文本都将被裁剪。

Label(text='long text . . . ', text_size=(200, None))

text_size 是一个 ListProperty,默认为 (None, None),表示默认情况下没有大小限制。

  • texture − 文本的纹理对象。当属性更改时,文本会自动重新渲染。texture 是一个 ObjectProperty,默认为 None。

  • texture_size − 文本的纹理大小。大小由字体大小和文本决定。如果 text_size 为 [None, None],则纹理将是适合文本所需的大小,否则它会被裁剪以适合 text_size。

  • underline − 为文本添加下划线。underline 是一个 BooleanProperty,默认为 False。

  • valign − 文本的垂直对齐方式。它是一个 OptionProperty,默认为 'bottom'。可用选项包括:'bottom'、'middle'(或 'center')和 'top'。

对齐方式

虽然 Label 类具有 halignvalign 属性,但文本图像(纹理)的大小仅足以使字符位于 Label 的中心。

valign 属性将不起作用,并且 halign 仅在文本包含换行符时才会起作用;即使 halign 设置为 left(默认值),单行文本也会显示为居中。

要使对齐属性生效,请设置 text_size,它是文本对齐所在的边界框的大小。例如,以下代码将此大小绑定到 Label 的大小,因此文本将在窗口部件边界内对齐。

Label:
   text_size: self.size
   halign: 'left'
   valign: 'middle'

标记

如果 Label 的 markup 属性为 True,则文本将使用 Text 标记进行渲染,用于内联文本样式。就像 html 标签一样,Text 标记标签具有 [tag],并且应该有一个对应的 [/tag] 关闭标签。例如 -

[b]Hello [color=ff0000]world[/color][/b]

可以使用以下标签构建标签文本:

序号 标签 & 标签文本描述
1 [b][/b]

激活粗体文本

2 [i][/i]

激活斜体文本

3 [u][/u]

下划线文本

4 [s][/s]

删除线文本

5 [font=<str>][/font]

更改字体(注意 - 这指的是 TTF 文件或已注册的别名)

6 [size=<size>][/size]

更改字体大小。应为整数,可以选择带单位(例如 16sp)

7 [color=#<color>][/color]

更改文本颜色

8 [sub][/sub]

相对于其之前的文本,将文本显示在脚注位置。

9 [sup][/sup]

相对于其之前的文本,将文本显示在尾注位置。

例如,这将创建一个标签 hello world,其中 world 为粗体

l = Label(text='Hello [b]World[/b]', markup=True)

大小调整

Label 的大小不受文本内容的影响,文本也不受大小的影响。为了控制大小调整,必须指定 text_size 来约束文本和/或将 size 绑定到 texture_size 以随文本一起增长。

例如在 kv 语言脚本中,此标签的大小将设置为文本内容(加上内边距):-

Label:
   size: self.texture_size

示例

我们现在将在以下示例中演示一些 Label 属性的使用。这里在垂直盒布局中放置了三个标签。每个标签都使用 Label 类的某些属性构建。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window

Window.size = (720, 350)

class LblApp(App):
   def build(self):
      layout = BoxLayout(orientation='vertical')
      self.l1 = Label(
         text='Hello World', color=[1, 0, 0, 1],
         font_size=40, bold=True
   )
   
   self.l2 = Label(
   text='Hello Python', color=[0, 1, 0, 1],
   font_size=40, italic=True
   )
   
   self.l3 = Label(
      text='Hello Kivy', color=[0, 0, 1, 1],
      font_size=40, font_name='Arial',
      underline=True
   )
   layout.add_widget(self.l1)
   layout.add_widget(self.l2)
   layout.add_widget(self.l3)
   return layout

if __name__ == '__main__':
   LblApp().run()

输出

运行上述程序时,将显示三个标签,其标题采用代码中定义的相应格式属性:-

Kivy Label

带 kv 文件的示例

现在将使用“kv”文件创建上述设计。除了上述 Python 代码中使用的属性外,我们还将背景颜色应用于每个标签。

Label 类不支持背景颜色属性。为了克服这个问题,我们在按钮的画布上绘制一个具有所需颜色的矩形作为其填充颜色。例如 -

Label:
   canvas:
      Color :
         rgba: 0, 1, 0, 0.25
      Rectangle:
         pos:self.pos
         size : self.size

我们利用此功能为三个标签应用不同的颜色作为背景。

BoxLayout:
   orientation:'vertical'
   Label:
      text:'Hello World'
      color : [1,0,0,1]
      font_size : 40
      bold : True
      canvas:
         Color :
            rgba: 0, 1, 0, 0.25
         Rectangle:
            pos:self.pos
            size : self.size

   Label:
      text:'Hello Python'
      color:[0,1,0,1]
      font_size:40
      italic:True
      canvas:
         Color :
            rgba: 1, 1, 0, 0.1
         Rectangle:
            pos:self.pos
            size : self.size
   Label:
      text:'Hello Kivy'
      color:[0,0,1,1]
      font_size:40
      font_name:'Arial'
      underline:True

输出

将此“lbl.kv”文件合并到 Python Kivy 应用程序中,然后运行程序。您应该会看到以下显示,显示三个具有相应背景颜色的标签。

Kivy Three Labels

Kivy - 文本输入

在桌面和 Web 应用程序中,您经常会看到一个矩形框,供用户输入一些文本。文本框是任何 GUI 工具包中必不可少的窗口部件。在 Kivy 中,TextInput 提供了一个控件,用户可以在其中输入和编辑文本。

可以自定义 TextInput 控件以接收单行或多行文本。可以使用鼠标选择文本的某一部分。还可以使用光标移动在其中执行全屏编辑。

TextInput 类定义在 kivy.uix.textinput 模块中。

from kivy.uix.textinput import TextInput
textbox = TextInput(**kwargs)

TextInput 类中定义了以下属性:

  • allow_copy − 决定是否允许复制文本。allow_copy 是一个 BooleanProperty,默认为 True。

  • background_color − 背景的当前颜色,格式为 (r, g, b, a)。它是一个 ColorProperty,默认为 [1, 1, 1, 1](白色)。

  • border − 用于 BorderImage 图形指令的边框。与 background_normal 和 background_active 一起使用。可用于自定义背景。它必须是一个包含四个值的列表:(bottom, right, top, left)。border 是一个 ListProperty,默认为 (4, 4, 4, 4)。

  • cursor − 表示当前光标位置的 (col, row) 值的元组。如果要移动光标,可以设置新的 (col, row)。滚动区域将自动更新以确保光标在视口中可见。cursor 是一个 AliasProperty。

  • cursor_color − 光标的当前颜色,格式为 (r, g, b, a)。cursor_color 是一个 ColorProperty,默认为 [1, 0, 0, 1]。

  • cut() − 将当前选择复制到剪贴板,然后将其从 TextInput 中删除。

  • delete_selection(from_undo=False) − 删除当前文本选择(如果有)。

  • disabled_foreground_color − 禁用时前景色当前颜色,格式为 (r, g, b, a)。disabled_foreground_color 是一个 ColorProperty,默认为 [0, 0, 0, 5](50% 透明黑色)。

  • font_name − 要使用的字体的文件名。路径可以是绝对路径或相对路径。相对路径由 resource_find() 函数解析。

  • font_name − 是一个 StringProperty,默认为 'Roboto'。此值取自 Config。

  • font_size − 文本的字体大小,单位为像素。font_size 是一个 NumericProperty,默认为 15 sp。

  • foreground_color − 前景的当前颜色,格式为 (r, g, b, a)。oreground_color 是一个 ColorProperty,默认为 [0, 0, 0, 1](黑色)。

  • halign − 文本的水平对齐方式。halign 是一个 OptionProperty,默认为 'auto'。可用选项包括:auto、left、center 和 right。

  • hint_text − 窗口部件的提示文本,如果文本为 '',则显示。hint_text 是一个 AliasProperty,默认为 ''。

  • hint_text_color − hint_text 文本的当前颜色,格式为 (r, g, b, a),ColorProperty,默认为 [0.5, 0.5, 0.5, 1.0](灰色)。

  • input_filter − 根据指定的模式过滤输入,如果未指定,则不应用任何过滤。它是一个 ObjectProperty,默认为 None。可以是 None、'int'(字符串)或 'float'(字符串)或可调用对象。

  • insert_text(substring, from_undo=False) − 在当前光标位置插入新文本。覆盖此函数以对输入验证的文本进行预处理。

  • line_height − 一行的高度。此属性会根据 font_name 和 font_size 自动计算。更改 line_height 不会产生任何影响。line_height 是一个 NumericProperty,只读。

  • line_spacing − 行之间占用的空间。line_spacing 是一个 NumericProperty,默认为 0。

  • minimum_height − TextInput 内部内容的最小高度。minimum_height 是一个只读 AliasProperty。

  • multiline − 如果为 True,则窗口部件将能够显示多行文本。如果为 False,则“Enter”键按下将使文本输入失去焦点,而不是添加新行

  • on_touch_down(touch) − 接收触摸按下事件。touch 参数是 MotionEvent 类的对象。它返回布尔值如果为 True,则触摸事件的分派将停止。如果为 False,则事件将继续分派到窗口部件树的其余部分。

  • on_touch_move(touch) − 接收触摸移动事件。touch 位于父级坐标中。

  • on_touch_up(touch) − 接收触摸抬起事件。touch 位于父级坐标中。

  • padding − 文本的内边距:[padding_left, padding_top, padding_right, padding_bottom]。Padding 也接受两个参数的形式 [padding_horizontal, padding_vertical] 和一个参数的形式 [padding]。Padding 是一个 VariableListProperty,默认为 [6, 6, 6, 6]。

  • password − 如果为 True,则窗口部件将显示其字符作为 password_mask 中设置的字符集。

  • password_mask − 设置在 password 为 True 时用于屏蔽文本的字符。password_mask 是一个 StringProperty,默认为 '*'。

  • paste() − 将系统剪贴板中的文本插入到 TextInput 中当前光标位置。

  • readonly − 如果为 True,则用户将无法更改文本输入的内容。

  • select_all() − 选择此 TextInput 中显示的所有文本。

  • select_text(start, end) − 选择此 TextInput 中显示的一部分文本。参数为 start - textinput.text 的索引,从中开始选择,以及 end - textinput.text 的索引,选择应显示到该索引为止

  • selection_color − 选择的当前颜色,格式为 (r, g, b, a)。

  • selection_from − 如果正在进行或已完成选择,则此属性将表示选择开始处的游标索引。

  • selection_text − 当前内容选择。selection_text 是一个 StringProperty,默认为 '',只读。

  • tab_width − 默认情况下,每个制表符将在文本输入窗口部件上替换为四个空格。您可以设置较低或较高的值。tab_width 是一个 NumericProperty,默认为 4。

  • text − 窗口部件的文本。它是一个 AliasProperty。

用法

创建简单的 hello world:-

widget = TextInput(text='Hello world')

如果要使用 Unicode 字符串创建窗口部件,请使用:-

widget = TextInput(text=u'My unicode string')

当用户在 TextInput 窗口部件内输入数据时,它将成为 text 属性的值。

当 TextInput 对象的 text 属性更改时,您可以调用回调。

def callback(instance, value):
   print('The widget', instance, 'have:', value)

textinput = TextInput()
textinput.bind(text=callback)

当 multiline 属性为 False 时,TextInput 接受单行输入。当用户按下 Enter 时,将生成 on_text_validate 事件:-

def callback(instance, value):
   print('The widget', instance, 'have:', value)
   
textinput = TextInput(multiline=False)
textinput.bind(on_text_validate=callback)

示例

让我们使用上面解释的一些 TextInput 类的属性和方法。在以下示例中,我们有两个多行文本框和两个按钮,它们排列在一个 BoxLayout 中。

COPY 按钮调用 gettext() 方法,该方法存储来自上文本框的选择的文本。

def gettext(self, instance):
   mydemoapp.text = self.text1.selection_text

PASTE 按钮调用回调 insert(),该回调将在光标位置粘贴选定的文本。

def insert(self, instance):
   self.text2.insert_text(mydemoapp.text)

这两个函数绑定到两个按钮:-

self.b1=Button(text='COPY')
self.b1.bind(on_press=self.gettext)
self.b2=Button(text='PASTE')
self.b2.bind(on_press=self.insert)

build() 方法用于组装文本框和按钮。

以下是完整代码 -

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.config import Config

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '300')
Config.set('graphics', 'resizable', '1')

class mydemoapp(App):
   text=''
   def gettext(self, instance):
      mydemoapp.text = self.text1.selection_text
   def insert(self, instance):
      self.text2.insert_text(mydemoapp.text)
      
   def build(self):
      main= BoxLayout(orientation= 'vertical')
      self.text1 = TextInput(multiline=True, font_size=20)
      btns = BoxLayout(orientation='horizontal')
      
      self.b1=Button(text='COPY')
      self.b1.bind(on_press=self.gettext)
      self.b2=Button(text='PASTE')
      self.b2.bind(on_press=self.insert)
      self.text2 = TextInput(
         multiline=True, font_size=20,
         foreground_color=[0,0,1,1]
      )
   
      btns.add_widget(self.b1)
      btns.add_widget(self.b2)
      main.add_widget(self.text1)
      main.add_widget(btns)
      main.add_widget(self.text2)
      return main
      
mydemoapp().run()

输出

Kivy Text Input

Kivy - 画布

与其他一些 GUI 工具包(例如 TKinter)不同,Kivy 没有独立的 Canvas 小部件。相反,您需要使用 Kivy 中每个小部件默认具有的画布。创建小部件时,可以创建在画布上绘制所需的所有指令。

需要注意的是,所有小部件都有不同的画布,但所有画布都在完全相同的绘图空间(即坐标空间)中绘制。此外,绘图空间不限于小部件的位置和大小。绘图空间的 (0,0) 始终是左下角。

"canvas.before" 属性在需要操作实例的颜色时特别有用。另一方面,canvas.after 用于在添加子项后执行任何指令。它在遍历所有子项后调用。

Kivy 中的 Canvas 是一组绘图指令。要进行绘制,您将需要一个 Canvas 对象和 Instruction 对象。例如,要在标签的画布上绘制矩形 -

from kivy.graphics import *
with label.canvas:
   Color(1., 0, 0)    # Add a red color

   # Add a rectangle
   Rectangle(pos=(10, 10), size=(500, 500))

如果您必须在不使用 Python 的上下文管理器的情况下执行相同的操作 -

label.canvas.add(Color(1., 0, 0))
label.canvas.add(Rectangle(size=(500, 500)))

您可以在任何小部件的画布上绘制矩形、椭圆、线和贝塞尔曲线等形状,包括 Widget 对象本身。(Widget 类是 Kivy 中所有其他可视小部件的基础)

要在画布上绘制矩形,我们需要指定 pos 和 size 属性 -

from kivy.graphics import Rectangle
Rectangle(**kwargs)

参数

  • pos - 指定矩形位置的 X 和 Y 坐标值列表,格式为 (x, y)。

  • size - 指定矩形宽度和高度的列表,格式为 (width, height)。

以下代码在 Widget 对象的画布上绘制一个矩形 -

widget=Widget()

with widget.canvas:
   Color(0,0,1,1)
   Rectangle(pos=(50,300), size_hint=(None, None), size=(300,200))

指令ColorRectangle 自动添加到 canvas 对象,并在绘制窗口时使用。

语法

您可能希望使用 "kv" 语言语法 -

Widget:
   canvas:
      color:
         rgb: 0,0,1
      Rectangle:
         pos: self.pos
         size: self.size

Kivy 中的 Label 对象没有背景颜色属性。要解决此问题,您可以在其画布上绘制一个填充所需颜色的矩形以提供背景。

lbl = Label(text='Hello World', font_size=24)
   with lbl.canvas.before:
      Color(1,1,0)
      Rectangle(pos=lbl.pos, size=lbl.size)

或者,使用 "kv" 脚本 -

Label:
   text:'Hello World'
   font_size:24
   canvas.before:
      Color:
         rgb: (1,1,0)
      Rectangle:
         pos:self.pos
         size=:self.size

语法

您可以使用以下语法在画布上绘制椭圆 -

from kivy.graphics import Ellipse
Ellipse(*args, **kwargs)

参数

  • segments - 定义绘制椭圆所需的段数。段数越多,如果段数很多,绘制会更平滑。

  • angle_start - 浮点数,默认为 0.0,并指定圆盘部分的起始角度(以度为单位)。

  • angle_end - 浮点数,默认为 360.0,指定圆盘部分的结束角度(以度为单位)。

  • angle_end - 椭圆的结束角度(以度为单位),默认为 360。

  • angle_start - 椭圆的起始角度(以度为单位),默认为 0。

  • pos - 画布坐标系中的位置

  • size - 椭圆的 X 和 Y 半径。如果两者相等,则成为圆形。

您还可以通过将图像文件作为矩形或椭圆的 source 属性直接在画布上绘制图像。

示例 1

以下代码实现了上面解释的所有绘图指令,以在小部件画布上显示矩形、椭圆和图像。它还在其画布上绘制了具有背景颜色的矩形的标签。

from kivy.app import App
from kivy.uix.image import Image
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import *
from kivy.core.window import Window

Window.size = (720,400)

class canvasdemoapp(App):
   def build(self):
      widget=Widget()

      # rectangle on canvas
      with widget.canvas:
         Color(0,0,1,1)
         Rectangle(
            pos=(50,300), size_hint=(None, None),
            size=(300,200)
         )
         Color(0.5, .2, 0.4, 1)
         d = 100

         # ellipse
         Ellipse(pos=(600,100), size=(d+75, d))
         Color(.5,.5,.5)

         # image
         Rectangle(source='kivy-logo.png', pos=(50,100))
         Color(1,1,1)
         Rectangle(source='TPlogo.png', pos=(300, 100))

         # label with background
         lbl = Label(
            text='Hello World', font_size=24,
            pos=(Window.width/2, 300), size =(200,200),
            color=(0,0,1,1)
         )
         with lbl.canvas.before:
            Color(1,1,0)
            Rectangle(pos=lbl.pos, size=lbl.size)
         widget.add_widget(lbl)
         btn=Button(
            text='Button', font_size=24,
            background_color= (.8, .4, .3, 1),
            pos=(500,10)
         )
         widget.add_widget(btn)
      return widget
canvasdemoapp().run()

输出

运行此代码时,将生成以下输出 -

Kivy Canvas

示例 2

Widget 对象响应所有触摸事件(例如 on_touch_down 和 on_touch_move 等)。

在下面的示例中,Widget 对象上 on_touch_down 事件的回调获取触摸事件发生的位置的坐标,并绘制一个圆圈(X 和 Y 半径相等的椭圆),并使用随机的 RGB 颜色值。

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
import random
from kivy.core.window import Window

Window.size = (720,300)
class widget(Widget):
   def on_touch_down(self, touch):
      colorR = random.randint(0, 255)
      colorG = random.randint(0, 255)
      colorB = random.randint(0, 255)
   
      self.canvas.add(Color(
         rgb=(colorR / 255.0, colorG / 255.0, colorB / 255.0)
      ))
   
      d = 30
      self.canvas.add(Ellipse(
         pos=(touch.x - d / 2, touch.y - d / 2),
         size=(d, d)
      ))
      
class circlesapp(App):
   def build(self):
      return widget()
circlesapp().run()

输出

运行程序并在不同位置生成 touch_down 事件。圆圈将绘制在位置上。

Kivy Canvas Circle

Kivy - 线

在 Kivy 库中,“Line” 是“kivy.graphics”模块中重要的顶点指令。在 Kivy 中,所有绘制都在与任何可用小部件关联的 Canvas 上完成。Line 指令绘制一条线或一系列线以及其他形状,如矩形、椭圆、贝塞尔曲线等。

必须注意,Kivy 绘图指令不是自动相对于小部件的位置或大小的。相反,所有小部件的画布共享一个公共坐标空间。

Line 函数需要一个数字值列表。数字的解释取决于将此列表分配到的参数。List 函数的参数是点、矩形、椭圆、贝塞尔曲线等。

绘制矩形

语法

您可以使用以下语法使用 Line 函数绘制矩形 -

with self.canvas:
   Line(rectangle=[x, y, w, h], width)

这里,“x” 和“y”表示矩形的左下角位置,“w” 和“h”表示宽度和高度。线条自动闭合。

您还可以使用 rounded_rectangle 属性构建具有圆角的矩形。参数必须是以下形式之一的元组 -

  • (x, y, width, height, corner_radius)

  • (x, y, width, height, corner_radius, resolution)

  • (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4)

  • (x, y, width, height, corner_radius1, corner_radius2, corner_radius3, corner_radius4, resolution)

示例

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.core.window import Window

Window.size = (720,300)

class drawRectangle(App):
   def build(self):
      widget = Widget()
      with widget.canvas:
         Color(1, 0, 1, 1)
         Line(
            rectangle=(50, 50, 300, 200),
            width=3
         )
         Line(rounded_rectangle=(500, 200, 300, 200, 20, 20, 20, 20))
      return widget
drawRectangle().run()

输出

它将生成以下输出窗口 -

Kivy Line

绘制椭圆

您需要将数字值列表分配给 Line 指令的 ellipse 属性。ellipse 参数的值必须是以下元组

(x, y, width, height, angle_start, angle_end, segments)

其中,

  • "x" 和 "y" 表示椭圆的左下角

  • "width" 和 "height" 表示椭圆的大小。如果这两个值相同,则结果将是一个圆圈

  • "angle_start" 和 "angle_end" 以度为单位。默认值为 0 和 360。

  • "segments" 是椭圆的精度。您可以使用此属性创建具有 3 个或更多边的多边形。小于 3 的值将不会表示。

示例

在下面的代码中,Line 指令用于使用不同的参数绘制椭圆 -

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.core.window import Window

Window.size = (720,400)

class drawEllipse(App):
   def build(self):
      widget = Widget()

      with widget.canvas:
         Color(0.5, .2, 0.4, 1)
         Line(ellipse=(500, 70, 200, 200), width=4)
         Line(ellipse=(100, 200, 100, 200), width=4)
         Color(.2, .8, 0, 1)
         Line(ellipse=(200, 100, 200, 100), width=4)
         Line(ellipse=(500, 300, 250, 90, 45, 270), width=3)

         Color(.1, .8, .3, 1)
         Line(ellipse=(200, 400, 200, 80, 180, 420, 30), width=5)

      return widget

drawEllipse().run()

输出

Kivy Line Ellipse

绘制贝塞尔曲线

贝塞尔曲线由一些控制点加权,我们将其包含在指令中。Line() 函数接受 Bezier 参数,并将 (x,y) 坐标对列表传递给该参数。参数必须是 2n 个元素的列表,“n”是点的数量。

with self.canvas:
   Line(bezier=[x1, y1, x2, y2, x5, y3], width)

需要注意的是,Line 指令函数的 points 参数也接收类似的 2n 个元素集。points 属性在连续点之间绘制一条线。

with self.canvas:
   Line(points=[x1, y1, x2, y2, x5, y3], width)

Line 指令的 point 参数仅绘制与每个 x-y 坐标对相对应的点,而没有任何连接它们的线。

with self.canvas:
   Line(point=[x1, y1, x2, y2, x5, y3], width)

示例

以下代码使用相同的“x”和坐标对集仅绘制点、线和贝塞尔曲线 -

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
from kivy.core.window import Window

Window.size = (720, 400)

class drawBezier(App):
   def build(self):
      widget = Widget()
      
      with widget.canvas:
         Color(0, 1, 1, 1)
         Bezier(
            points=[700, 400, 450, 300, 300, 350, 350, 200, 200, 100, 150, 10],
            segments=20
         )
         Color(1, 1, 0, 1)
         Point(
            points=[700, 400, 450, 300, 300, 350, 350, 200, 200, 100, 150, 10],
            pointsize=5
         )

         Color(.1, .1, .8)
         Line(
            points=[700, 400, 450, 300, 300, 350, 350, 200, 200, 100, 150, 10],
            pointsize=3
         )
      return widget
      
drawBezier().run()

输出

运行此代码时,将生成一个类似于此处所示的输出窗口 -

Kivy Draw Bezier Curve

需要注意的是,Kivy 提供了另一组顶点指令来使用 Rectangle、Ellipse、Bezier 指令绘制这些形状。这些与 Line 指令的 rectangle、ellipse 和 bezier 参数不同。

注意指令本身和参数的大写第一个字母(Ellipse 指令与 Line 指令的 ellipse 参数)。Line 函数在不重新计算点的情况下绘制形状。

Kivy - 复选框

在任何 GUI 工具包中,复选框用于使用户能够从可用选项中选择一个或多个选项。在 Kivy 中,可以配置 CheckBox 以使选择互斥(仅可以选择一个可用选项),或允许用户标记任意数量的选择。

  • 如果两个或多个复选框的 group 属性具有相同的值,则它们将显示为圆形单选按钮;用户只能选择一个选项,因为只有一个复选框的 active 属性可以为 True,对于其他复选框,active 属性将自动变为 False。

  • 对于没有 group 属性的复选框,它将显示为一个矩形框,按下时,将显示一个复选标记,active 属性变为 True。再次点击它,复选标记将被移除,active 属性变为 False。

CheckBox 类定义在 kivy.uix.checkbox 模块中

from kivy.uix.checkbox import CheckBox
cb = CheckBox(**kwargs)

如果复选框对象绑定到其 active 属性,则每次 active 属性更改时都会调用回调。

checkbox = CheckBox()
checkbox.bind(active=callback)

示例

以下 Python 代码演示了如何使用互斥的复选框以及多选复选框。

代码使用一个垂直 BoxLayout,其中包含两个水平布局和两个标签。上部水平布局包含两个复选框,这两个复选框的 group 属性均为“sex”

self.m = CheckBox(group='sex', color=[1,0,1,1])
self.m.bind(active=self.on_male)
gendergrp.add_widget(self.m)
gendergrp.add_widget(Label(text='Female'))
self.f = CheckBox(active=False, group='sex')
self.f.bind(active=self.on_female)
gendergrp.add_widget(self.f)

这两个复选框都调用一个回调方法,该方法识别 active 属性。

下部水平框包含三个独立的复选框 -

interests.add_widget(Label(text='Sports'))
self.cb1 = CheckBox()
self.cb1.bind(active=self.on_interest)
interests.add_widget(self.cb1)

self.cb2 = CheckBox()
self.cb2.bind(active=self.on_interest)
interests.add_widget(Label(text='Music'))
interests.add_widget(self.cb2)

self.cb3 = CheckBox()
self.cb3.bind(active=self.on_interest)
interests.add_widget(Label(text='Travel'))
interests.add_widget(self.cb3)

单击每个复选框时,都会构建一个选定兴趣列表,并在水平框下方的标签上显示。

以下是完整代码 -

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.checkbox import CheckBox
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window

Window.size = (720,400)
class CheckBoxApp(App):
   gender=''
   intrst=[]
   def on_male(self, instance, value):
      if value:
         CheckBoxApp.gender='Male'
         self.lbl.text = "Gender selected: "+CheckBoxApp.gender
      else:
         self.lbl.text = "Gender selected: "
   def on_female(self, instance, value):
      if value:
         CheckBoxApp.gender='Female'
         self.lbl.text = "Gender selected: "+CheckBoxApp.gender
      else:
         self.lbl.text = "Gender selected: "
   def on_interest(self, instance, value):
      CheckBoxApp.intrst=[]
      if self.cb1.active:
         CheckBoxApp.intrst.append("Sports")
      if self.cb2.active:
         CheckBoxApp.intrst.append("Music")
      if self.cb3.active:
         CheckBoxApp.intrst.append("Travel")
      self.lbl1.text="Interests Selected: "+" ".join(CheckBoxApp.intrst)

   def build(self):
      main=BoxLayout(orientation='vertical')

      gendergrp=BoxLayout(orientation='horizontal')
      interests = BoxLayout(orientation='horizontal')
      
      gendergrp.add_widget(Label(text='Gender:'))
      gendergrp.add_widget(Label(text='Male'))
      self.m = CheckBox(group='sex', color=[1,0,1,1])
      self.m.bind(active=self.on_male)
      gendergrp.add_widget(self.m)
      gendergrp.add_widget(Label(text='Female'))
      self.f = CheckBox(active=False, group='sex')
      self.f.bind(active=self.on_female)
      
      gendergrp.add_widget(self.f)
      main.add_widget(gendergrp)
      self.lbl = Label(text="Gender selected: ", font_size=32)
      
      main.add_widget(self.lbl)
      
      interests.add_widget(Label(text='Interests:'))
      interests.add_widget(Label(text='Sports'))
      self.cb1 = CheckBox()
      self.cb1.bind(active=self.on_interest)
      interests.add_widget(self.cb1)
      
      self.cb2 = CheckBox()
      self.cb2.bind(active=self.on_interest)
      interests.add_widget(Label(text='Music'))
      interests.add_widget(self.cb2)
      
      self.cb3 = CheckBox()
      self.cb3.bind(active=self.on_interest)
      interests.add_widget(Label(text='Travel'))
      interests.add_widget(self.cb3)
      
      self.lbl1 = Label(text="Interests selected: ", font_size=32)
      main.add_widget(interests)
      main.add_widget(self.lbl1)
      
      return main

if __name__ == '__main__':
   CheckBoxApp().run()

输出

运行此代码时,将生成一个类似于此处所示的 GUI -

Kivy Checkbox

Kivy - 下拉列表

Kivy 中的下拉小部件与其他 GUI 工具包中的类似小部件完全不同。Kivy 的下拉菜单不仅显示标签,还显示其他任何小部件,例如按钮、图像等。

DropDown 类定义在“kivy.uix.dropdown”模块中。

from kivy.uix.dropdown import DropDown
dropdown=DropDown()

构建下拉菜单对象需要执行以下步骤 -

  • 在此对象中添加其他小部件时,我们需要通过禁用 size_hint 手动指定高度,从而下拉菜单计算所需区域。

  • 对于在 DropDown 中添加的每个子小部件,您都需要附加一个回调,该回调将在下拉菜单上调用 select() 方法。绑定每个子项并添加到下拉菜单对象。

  • 将下拉菜单添加到主按钮并将其与下拉菜单类的 open() 方法绑定

  • 最后,运行应用程序并单击主按钮。您将看到一系列子小部件下拉。单击其中任何一个以调用其关联的回调。

示例

在此示例中,我们将演示 Kivy 中的下拉小部件是如何工作的。我们将使用 for 循环将十个按钮添加到下拉菜单中,如下所示 -

dropdown = DropDown()

for index in range(1, 11):
   btn = Button(text ='Button '+str(index),
      size_hint_y = None, height = 40)
   btn.bind(on_release = lambda btn: dropdown.select(btn.text))
   dropdown.add_widget(btn)

box.add_widget(dropdown)

我们将主按钮放置在 BoxLayout 中,将下拉菜单对象添加到其中,并将主按钮与下拉菜单对象的 open() 方法绑定。

box = BoxLayout(orientation='vertical')
mainbutton = Button(text ='Drop Down Button', size_hint=(None, None), size =(250, 75), pos_hint ={'center_x':.5, 'top':1})
box.add_widget(mainbutton)
mainbutton.add_widget(dropdown)
mainbutton.bind(on_release = dropdown.open)

最后,我们需要侦听下拉列表中的选择并将数据分配给按钮文本。

dropdown.bind(on_select = lambda instance, x: setattr(mainbutton, 'text', x))

所有这些步骤都包含在以下代码中 App 类的 build() 方法中 -

from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window

Window.size = (720, 400)

class Drop_down_app(App):
   def build(self):
      box = BoxLayout(orientation='vertical')

      mainbutton = Button(
         text='Drop Down Button', size_hint=(None, None),
         size=(250, 75), pos_hint={'center_x': .5, 'top': 1}
      )
      box.add_widget(mainbutton)
      dropdown = DropDown()
      for index in range(1, 11):
         btn = Button(text='Button ' + str(index),
            size_hint_y=None, height=40)
         btn.bind(on_release=lambda btn: dropdown.select(btn.text))
      dropdown.add_widget(btn)
      box.add_widget(dropdown)
      
      mainbutton.add_widget(dropdown)
      mainbutton.bind(on_release=dropdown.open)

      dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
      return box
      
Drop_down_app().run()

输出

当我们运行上述代码时,主按钮可见。

Kivy Dropdown List

单击按钮。结果,按钮列表下拉。当单击列表中的按钮时,主按钮的标题将更改为该按钮的标题。

Kivy Dropdown Button

Kivy - 窗口

Window 类是 Kivy 框架的核心类之一。应用程序窗口是通过在特定布局中放置一个或多个小部件来构建的。build() 方法通常返回一个单一的根小部件,它可能是以树状结构排列的多个其他小部件的组合。相反,小部件树的根可以直接添加到应用程序窗口。

Window 类定义在“kivy.core.window”模块中。它继承了 WindowBase 类,WindowBase 类是任何窗口实现的抽象窗口小部件。默认应用程序窗口是在 App 对象启动其事件循环时创建的。请注意,Kivy 每个应用程序仅支持一个窗口。

许多 Window 属性是从 Kivy 配置文件(KIVY_HOME 目录中的“config.ini”文件)中读取的。

小部件尺寸取决于默认窗口的大小。App 类 build() 方法中的以下代码将小部件树放入应用程序窗口中。请注意,build() 方法不会返回根小部件。相反,它被添加到 Window 对象的 add_widget() 方法中。

box=BoxLayout(orientation='vertical')

l=Label(text='Window Properties', font_size=32)
box.add_widget(l)

b1=ToggleButton(text='Fullscreen')
b2=ToggleButton(text='Border')
b3=ToggleButton(text='Position')

bh=BoxLayout(orientation='horizontal', size_hint=(1, .2))
bh.add_widget(b1)
bh.add_widget(b2)
bh.add_widget(b3)

box.add_widget(bh)
Window.add_widget(box)

事件

Window 对象可以识别不同类型的事件 -

  • 当分发新的 MotionEvent 时,会触发 on_motion 事件。

  • Window 吸收触摸事件 on_touch_down, on_touch_move, on_touch_up 等。

  • 当 Window 关闭时,会触发 on_close 事件。

  • 当用户想要通过按下标题栏上的关闭按钮来结束事件循环时,会发生 on_request_close 事件。

  • 当光标进入窗口时,会触发 on_cursor_enter 事件。

  • 类似地,当光标离开窗口时,会发生 on_cursor_leave 事件。

  • 当窗口分别最小化和最大化时,会触发 on_minimize 和 on_maximize 事件。

  • 当窗口恢复时,会触发 on_restore 事件。

类似于触摸事件,当按键按下或释放时,按键事件 on_key_down 和 on_key_up 事件分别发出按键、扫描码、代码点、修饰符。

对于本章中的演示示例,让我们将一些 Window 事件与回调方法绑定。

Window.bind(on_request_close = self.on_close)
Window.bind(on_cursor_leave=self.on_leave)
Window.bind(on_cursor_enter=self.on_enter)

每当鼠标指针离开窗口区域时,都会调用 on_leave() 方法。

def on_leave(self, *args):
   print ("leaving the window")

类似地,当鼠标进入窗口区域时,将调用 on_enter 回调。

def on_enter(self, *args):
   print ("Entering the window")

当用户选择关闭事件循环时,会引发 on_request_close 事件。如果用户按下 X 按钮,以下回调会询问用户是否要退出。您还可以使弹出窗口出现。

def on_close(self, instance):
   resp=input("Do you want the window to close?")
   if resp=='y': Window.close()

属性

应用程序窗口的外观由 Window 类中定义的许多属性决定。它们的默认值由 config.ini 文件提供。但是,它们可以在应用程序代码中修改。一些 Wiindow 属性如下所示 -

  • borderless - 当设置为 True 时,此属性将删除窗口边框/装饰。

  • children - 返回此窗口子级的列表。

  • clearcolor - 用于清除窗口的颜色。clear() 方法使用此属性与此颜色值一起使用

from kivy.core.window import Window
Window.clearcolor = (1, 0, 0, 1)
Window.clear()
  • custom_titlebar - 当设置为 True 时,允许用户设置一个 widget 作为标题栏。

  • fullscreen - 此属性设置窗口的全屏模式。可用选项包括:True、False、'auto' 和 'fake'。

  • left , top - 窗口的左和上位置。它是 SDL2 属性,左上角为 [0, 0]。

  • size - 获取/设置窗口的大小。

from kivy.core.window import Window
Window.size = (720,400)

您还可以通过修改配置值来设置大小 -

from kivy.config import Config
Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

让我们处理一些 Window 属性。在这个程序的应用程序窗口上,我们有三个切换按钮。我们将它们绑定到某些回调。

b1.bind(on_press=self.pressed)
b2.bind(on_press=self.bordered)
b3.bind(on_press=self.positioned)

pressed() 方法在全屏和普通之间切换全屏状态。

def pressed(self, instance):
   if instance.state=='down':
      Window.set_title("Kivy Full screen window")
      Window.maximize()
   elif instance.state=='normal':
      Window.set_title('Kivy Restored Window')
      Window.restore()

当按下 b2 按钮时,bordered() 方法使窗口无边框,释放时恢复为原始边框窗口。

def bordered(self, instance):
   print (Window.top, Window.left)
   if instance.state=='down':
      Window.borderless=True
   elif instance.state=='normal':
      Window.borderless=False

当按下/释放 b3 时,positioned() 回调将窗口移动到 (0,0) 位置并返回到其早期位置。

def positioned(self, instance):
   print (Window.top, Window.left)
   if instance.state=='down':
      self.x, self.y=Window.left, Window.top
      Window.left, Window.top=(0,0)
   elif instance.state=='normal':
      Window.left, Window.top=self.x,self.y

应用程序窗口首先显示如下。生成事件(鼠标离开、进入 on_request_close)并查看回调的运行情况。同样检查切换按钮的动作。

Kivy Windows

Kivy - 滚动视图

Kivy 框架中的 ScrollView widget 包含任何其他尺寸大于分配给它的尺寸的 widget,并为其提供一个可滚动的面板。这使得包含的 widget 可以垂直或水平平移/滚动。

ScrollView 类在 kivy.uix.scrollview 模块中定义。通常,您在一个布局中组合一个或多个 widget,并将布局添加到 ScrollView。

from kivy.uix.scrollview import ScrollView
view = ScrollView()
view.add_widget(layout)

ScrollView 对象的“scroll_x”和“scroll_y”属性控制滚动面板的滚动行为。ScrollView 允许双向滚动。您可以通过将“do_scroll_x”或“do_scroll_y”设置为 False 来禁用某个轴上的滚动。

此外,“scroll_distance”属性设置要移动的最小距离,默认为 20 像素。此外,scroll_timeout 属性指定最大时间段,默认为 55 毫秒。

“scroll_distance”和“scroll_timeout”的重要性在于:如果触摸手势滚动的像素数大于或等于 scroll_distance,并且在 scroll_timeout 时间段内,则将其识别为滚动手势,并且将开始平移(滚动/平移)。如果超时发生,则触摸按下事件将传播到子级。

ScrollView 类的其他属性如下所示 -

  • do_scroll - 允许在 X 或 Y 轴上滚动。

  • do_scroll_x - 允许在 X 轴上滚动。这是一个 BooleanProperty,默认为 True。

  • do_scroll_y - 允许在 Y 轴上滚动。这是一个 BooleanProperty,默认为 True。

  • scroll_distance - 开始滚动 ScrollView 之前要移动的距离(以像素为单位)。这是一个 NumericProperty,默认为 20 像素。

  • scroll_timeout - 触发 scroll_distance 允许的超时时间(以毫秒为单位),默认为 55 毫秒

  • scroll_to() - 滚动视口以确保给定的 widget 可见,可以选择添加填充和动画。

  • scroll_type - 设置用于 scrollview 内容的滚动类型。可用选项包括:['content']、['bars']、['bars', 'content']。

  • ScrollView 对象发出以下事件 -

    • on_scroll_start - 从触摸开始滚动时触发的通用事件。

    • on_scroll_move - 从触摸移动滚动时触发的通用事件。

    • on_scroll_stop - 从触摸停止滚动时触发的通用事件。

示例

为了能够理解 ScrollView 的工作原理,我们需要一个足够大的布局,以便它溢出主应用程序窗口的尺寸。为此,我们将 100 个按钮添加到一个单列网格布局中,并对其应用 scrollview。

以下示例中的操作代码如下所示 -

layout = GridLayout(cols=1)

for i in range(100):
   btn = Button(text='Button ' + str(i), size_hint_y=None, height=40)
   layout.add_widget(btn)

root = ScrollView()
root.add_widget(layout)

为了确保高度足以滚动,将布局对象的 minimum_height 属性绑定到其 setter。

layout.bind(minimum_height=layout.setter('height'))

以下是 ScrollView 演示代码的完整代码 -

from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.app import App
from kivy.core.window import Window

Window.size = (720, 350)

class scrollableapp(App):
   def build(self):
      layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
      layout.bind(minimum_height=layout.setter('height'))

      for i in range(100):
         btn = Button(text='Button ' + str(i), size_hint_y=None, height=40)
         layout.add_widget(btn)
      
      root = ScrollView(
         size_hint=(1, None),
         size=(Window.width, Window.height)
      )
      root.add_widget(layout)
      return root

scrollableapp().run()

输出

运行上述代码。要超出视图中可见的按钮,请使用鼠标或手指垂直滚动(如果您正在使用触摸屏设备)。

Kivy Scrollview

Kivy - 旋转木马

Carousel 是一种用于循环浏览一系列内容的幻灯片。Kivy 框架包含一个 Carousel widget,它允许您轻松创建可浏览的幻灯片,尤其适用于智能手机等触摸屏设备。Carousel 中的页面可以水平或垂直移动。

Carousel 类在“kivy.uix.carousel”模块中定义。

from kivy.uix.carousel import Carousel
carousel = Carousel(**kwargs)

下面给出了一个使用 Carousel 创建简单幻灯片的 Python/Kivy 程序 -

from kivy.app import App
from kivy.uix.carousel import Carousel
from kivy.uix.image import Image

class CarouselApp(App):
   def build(self):
      carousel = Carousel(direction='right')
      img1=Image(source='1.png')
      carousel.add_widget(img1)
      img2=Image(source='2.png')
      carousel.add_widget(img2)
      img3=Image(source='3.png')
      carousel.add_widget(img3)
      return carousel
CarouselApp().run()

您还可以使用“kv”语言脚本构建 Carousel。

Carousel:
   direction: 'right'
   Image:
      source: '1.png'
   Image:
      source: '2.png'
   Image:
      source: '3.png'
   Image:
      source: '4.png'

Carousel 类定义了以下属性 -

  • current_slide - 当前显示的幻灯片。current_slide 是一个 AliasProperty。

  • direction - 指定幻灯片排序的方向。这对应于用户从哪个方向滑动以从一个幻灯片转到下一个幻灯片的方向。它可以是 right、left、top 或 bottom。

  • index - 根据索引获取/设置当前幻灯片。index 默认为 0(第一个项目)。

  • load_next(mode='next') - 动画到下一张幻灯片。

  • load_previous() - 动画到上一张幻灯片。

  • load_slide(slide) - 动画到作为参数传递的幻灯片。

  • loop - 允许 Carousel 无限循环。如果为 True,当用户尝试滑动到最后一页之外时,它将返回到第一页。如果为 False,它将保留在最后一页。

  • next_slide - Carousel 中的下一张幻灯片。如果当前幻灯片是 Carousel 中的最后一张幻灯片,则为 None。

  • previous_slide - Carousel 中的上一张幻灯片。如果当前幻灯片是 Carousel 中的第一张幻灯片,则为 None。

  • scroll_distance - 开始滚动 Carousel 之前要移动的距离(以像素为单位)。默认距离为 20dp。

  • scroll_timeout - 触发 scroll_distance 允许的超时时间(以毫秒为单位)。默认为 200(毫秒)

  • slides - Carousel 内部的幻灯片列表。

示例

例如,在 Kivy 中使用 Carousel,请查看以下代码。Carousel 对象用作应用程序的根 widget,我们将标签、按钮和图像作为其幻灯片添加。

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.carousel import Carousel
from kivy.uix.image import Image
from kivy.core.window import Window
Window.size = (720,350)

class CarouselApp(App):
   def build(self):
      carousel = Carousel(direction='right')
      carousel.add_widget(Button(text='Button 1', font_size=32))
      src = "ganapati.png"
      image = Image(source=src, fit_mode="contain")
      carousel.add_widget(image)
      carousel.add_widget(Button(text="Button 2", font_size=32))
      return carousel
CarouselApp().run()

输出

这是一个简单的应用程序,可以通过在设备的显示屏上滑动来浏览一系列幻灯片。direction 参数设置为 right,这意味着后续幻灯片位于右侧。

Kivy Carousel

使用“kv”语言组装 Carousel

让我们使用“kv”语言脚本组装 Carousel。这次,direction 设置为 top,这意味着您必须向上滑动屏幕才能看到下一个显示。

示例

Carousel:
   direction:'top'
   Button:
      text:'Button 1'
      font_size:32
   Image:
      source:"kivy-logo.png"
      fit_mode:"contain"
   Button:
      text:"Button 2"
      font_size:32

输出

幻灯片一个接一个地堆叠。

Kivy Carousel Button

Kivy - 滑块

在 Kivy 框架中,当您想要设置连续变化的数字属性的值时,Slider widget 是一个非常有用的控件。例如,电视屏幕或移动设备或扬声器声音的亮度。

滑块 widget 的外观是一个水平或垂直的条形,上面有一个滑块,用于设置值。当滑块位于水平滑块的左侧时,它对应于最小值;当它位于最右侧时,它对应于最大值。

Slider 类在“kivy.uix.slider”类中定义。

from kivy.uix.slider import Slider slider = Slider(**kwargs)

要在 Kivy 中创建基本的滑块控件,我们可以使用以下代码 -

from kivy.uix.slider import Slider
s = Slider(min=0, max=100, value=25)

滑块控件的默认方向是水平方向。如果需要,请设置 orientation='vertical'。您应该在 Kivy 应用程序窗口上看到如下所示的滑块。

Kivy Slider

可以使用鼠标或触摸(在触摸屏的情况下)沿滑块移动滑块。

要根据滑块值的更改调用某个操作,请将 value 属性绑定到某个回调。

def on_value_changed(self, instance, val):
   print (val)
   
s.bind(value = on_value_changed)

以下是 Slider 类的一些重要属性 -

  • max - 允许的最大值。max 是一个 NumericProperty,默认为 100。

  • min - 允许的最小值。min 是一个 NumericProperty,默认为 0。

  • orientation - 滑块的方向。orientation 是一个 OptionProperty,默认为 'horizontal'。可以取值为 'vertical' 或 'horizontal'。

  • padding − 滑块的填充。填充用于图形表示和交互。它防止光标超出滑块边界框的范围。padding 是一个 NumericProperty,默认为 16sp。

  • range − 滑块的范围,格式为 (最小值,最大值),是 (min, max) 属性的 ReferenceListProperty。

  • sensitivity − 触摸是否与小部件的整个主体碰撞,还是仅与滑块手柄部分碰撞。sensitivity 是一个 OptionProperty,默认为 'all'。可以取值为 'all' 或 'handle'。

  • step − 滑块的步长。确定滑块在 min 和 max 之间每次移动的间隔或步长的大小。step 是一个 NumericProperty,默认为 0。

  • value − 滑块当前使用的值。value 是一个 NumericProperty,默认为 0。

  • value_track − 决定滑块是否应该绘制表示 min 和 value 属性值之间空间的线。它是一个 BooleanProperty,默认为 False。

  • value_track_color − value_line 的颜色,采用 rgba 格式。value_track_color 是一个 ColorProperty,默认为 [1, 1, 1, 1]。

  • value_track_width − 轨迹线的宽度,默认为 3dp。

示例

在下面的代码中,我们使用三个滑块小部件来让用户设置所需颜色的 RGB 值。滑块的值用于更改 RGB 值。例如,红色滑块的更改会设置 'r' 分量的值 -

def on_red(self, instance, val):
   self.r = int(val)/255
   self.colour=[self.r, self.g, self.b, self.t]
   self.redValue.text = str(int(val))

绿色和蓝色的类似回调也进行了编码。这些被分配给 colur 变量的值,colur 变量是 ColorProperty 类型。它绑定到 Label 小部件的颜色属性。

colour = ColorProperty([1,0,0,1])

def on_colour_change(self, instance, value):
   self.ttl.color=self.colour

因此,通过滑块设置 RGB 值会更改 Label 的文本颜色。

三个滑块控件以及所需的标签放置在一个具有四列的内部网格布局中。一个具有一个列的上层网格包含一个标签,该标签的颜色希望随着滑块移动而更改。

完整代码如下所示:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.slider import Slider
from kivy.uix.label import Label
from kivy.properties import ColorProperty, NumericProperty
from kivy.core.window import Window

Window.size = (720, 400)

class SliderExample(App):
   r = NumericProperty(0)
   g = NumericProperty(0)
   b = NumericProperty(0)
   t = NumericProperty(1)
   colour = ColorProperty([1, 0, 0, 1])

   def on_colour_change(self, instance, value):
      self.ttl.color = self.colour

   def on_red(self, instance, val):
      self.r = int(val) / 255
      self.colour = [self.r, self.g, self.b, self.t]
      self.redValue.text = str(int(val))
   
   def on_green(self, instance, val):
      self.g = int(val) / 255
      self.colour = [self.r, self.g, self.b, self.t]
      self.greenValue.text = str(int(val))
   
   def on_blue(self, instance, val):   
      self.b = int(val) / 255
      self.colour = [self.r, self.g, self.b, self.t]
      self.blueValue.text = str(int(val))
   
   def build(self):
      maingrid = GridLayout(cols=1)
      self.ttl = Label(
         text='Slider Example',
         color=self.colour, font_size=32
      )
      maingrid.add_widget(self.ttl)
      grid = GridLayout(cols=4)
      self.red = Slider(min=0, max=255)
      self.green = Slider(min=0, max=255)
      self.blue = Slider(min=0, max=255)
      grid.add_widget(Label(text='RED'))
      grid.add_widget(self.red)
      grid.add_widget(Label(text='Slider Value'))
      self.redValue = Label(text='0')
      grid.add_widget(self.redValue)
      self.red.bind(value=self.on_red)
      
      grid.add_widget(Label(text='GREEN'))
      grid.add_widget(self.green)
      grid.add_widget(Label(text='Slider Value'))
      self.greenValue = Label(text='0')
      grid.add_widget(self.greenValue)
      self.green.bind(value=self.on_green)
      
      grid.add_widget(Label(text='BLUE'))
      grid.add_widget(self.blue)
      grid.add_widget(Label(text='Slider Value'))
      self.blueValue = Label(text='0')
      grid.add_widget(self.blueValue)
      self.blue.bind(value=self.on_blue)
      self.bind(colour=self.on_colour_change)
      maingrid.add_widget(grid)
      return maingrid

root = SliderExample()
root.run()

输出

运行此代码时,将生成一个类似于此处所示的输出窗口 -

Kivy Slider Move

Kivy - 图片

能够显示图像是在任何 GUI 应用程序中必不可少的。Kivy 框架包含 Image 小部件作为图像容器。它能够从 png、jpg 和 GIF 文件加载图像数据。对于 SVG 文件,您可能需要使用另一个名为 Svg 本身的小部件。

Kivy 包含两个图像小部件 - ImageAsyncImage。它们在“kivy.uix.image”模块中定义。

Image 小部件用于加载本地计算机中可用的图像文件。

from kivy.uix.image import Image
img = Image(source = 'logo.png')

要从任何外部源加载任何图像,您需要使用 AsyncImage 小部件。AsyncImage 类是 Image 类的子类。

from kivy.uix.image import AsyncImage
img = AsyncImage(source = 'http://xyz.com/logo.png')

如果您需要通过从 URL 检索图像来显示图像,AsyncImage 会在后台线程中执行此操作,而不会阻塞您的应用程序。

Image 类定义了以下属性 -

  • source − 图像的文件名/源。source 是一个 StringProperty,默认为 None。

  • fit_mode − 如果图像的大小与小部件的大小不同,则此属性决定如何调整图像大小以适合小部件框内。

可用选项

  • scale-down − 对于大于 Image 小部件尺寸的图像,图像将缩小以适合小部件框内,保持其纵横比且不拉伸。如果图像的大小小于小部件,则将以其原始大小显示。

  • fill − 图像将拉伸以填充小部件,无论其纵横比或尺寸如何。如果图像的纵横比与小部件不同,则此选项可能导致图像失真。

  • contain − 图像调整大小以适合小部件框内,保持其纵横比。如果图像大小大于小部件大小,则行为将类似于“scale-down”。但是,如果图像大小小于小部件大小,与“scale-down”不同,图像将调整大小以适合小部件内。

  • cover − 图像将水平或垂直拉伸以填充小部件框,保持其纵横比。如果图像的纵横比与小部件不同,则图像将被裁剪以适合。

  • texture − 图像的纹理对象。纹理表示原始的加载图像纹理。它根据 fit_mode 属性在渲染期间进行拉伸和定位。

  • texture_size − 图像的纹理大小。这表示原始的加载图像纹理大小。

  • color − 图像颜色,格式为 (r, g, b, a)。此属性可用于“着色”图像。但是,如果源图像不是灰色/白色,则颜色将无法按预期工作。

  • image_ratio − 一个只读属性,返回图像的比例(宽度 / float(高度))。

  • reload() − 从磁盘重新加载图像。这有助于在图像内容更改的情况下从磁盘重新加载图像。

img = Image(source = '1.jpg')
img.reload()

示例

在以下示例代码中,我们主要尝试演示 fit_mode 属性的效果。下面给出了一个“kv”语言脚本,该脚本在轮播小部件中显示不同的图像。每个图像都有一个不同的 fit_mode 属性值。

“kv”语言脚本为 -

Carousel:
   direction:'top'
   Image:
      source:'1.png'
      fit_mode:"scale-down"
   Image:
      source:"TPlogo.png"
      fit_mode:"contain"
   Image:
      source:"TPlogo.png"
      fit_mode:"fill"
   Image:
      source:"TPlogo.png"
      fit_mode:"cover"

输出

在 Kivy App 类中加载此脚本并运行它后,将根据相应的 fit_mode 显示不同的图像 -

fit_mode = scaled-down

源大于 Image 小部件。

kivy images

fit_mode=contain

源图像小于 Image 小部件。

source image smaller

fit_mode: fill

图像调整大小以适合而不丢失纵横比。

Kivy Image Resized

fill_mode=cover

图像拉伸以覆盖整个小部件区域。

Kivy Image Stretched

Kivy - 弹出窗口

Kivy 中的 Popup 小部件呈现一个对话框,该对话框出现在主父窗口之上,通常是响应按钮点击事件而出现的。对话框用于多种目的,例如向用户显示特定消息、让用户输入某些内容或询问用户是否确认特定操作。

通常,任何 GUI 应用程序中的对话框有两种类型:模态和非模态。不允许用户在不与之交互的情况下与父窗口交互的对话框称为模态对话框。另一方面,如果用户可以在不与之交互的情况下关闭对话框,则它是非模态对话框。

在 Kivy 中,弹出对话框默认覆盖整个父窗口。您可以根据需要配置其大小。

Popup 类在“kivy.uix.popup”模块中定义。

from kivy.uix.popup import Popup
popup = Popup(**kwargs)

Popup 对象预配置了一个布局,该布局具有标题和分隔线。我们可以通过向其布局参数添加其他小部件来自定义布局。

以下代码段在父窗口上生成一个简单的弹出窗口 -

from kivy.uix.popup import Popup
popup = Popup(title='Popup Demo',
   content=Label(text='This is a Popup'),
   size_hint=(None, None), size=(400, 400))

您需要调用 Popup 对象的 open() 方法来显示它。

popup.open()

当弹出窗口显示时,它将被其外部的任何点击关闭。要防止弹出窗口自动关闭,请将 auto_dismiss 属性设置为 False。您需要显式调用 popup.dismiss() 方法。通常,这是通过将其绑定到按钮的 on_press 事件来完成的。

class popdemo(App):
   def build(self):
      btn = Button(text="Click here")
      btn.bind(on_press=self.onButtonPress)
      return btn
   
   def onButtonPress(self, button):
      layout = GridLayout(cols=1)
      lbl = Label(text='Hello world')
      closeButton = Button(text="OK")
      layout.add_widget(lbl)
      layout.add_widget(closeButton)
      popup = Popup(
         title='Popup Demo', content=layout,
         auto_dismiss=False, size_hint=(None, None),
         size=(400, 400)
      )
      popup.open()
      closeButton.bind(on_press=self.on_close)
   
   def on_close(self, event):
      self.popup.dismiss()

当单击标题为“点击此处”的按钮时,您将看到如下所示的弹出对话框 -

Kivy Popup

按弹出窗口上的“确定”按钮将其关闭。

Popup 类定义了以下属性 -

  • content − 弹出窗口的内容,显示在标题下方。content 是一个 ObjectProperty,默认为 None。

  • title − 表示弹出窗口标题的字符串。title 是一个 StringProperty,默认为“无标题”。

Popup 对象响应以下事件 -

  • on_open − 弹出窗口打开时触发。

  • on_dismiss − 弹出窗口关闭时触发。如果回调返回 True,则关闭操作将被取消。

示例

以下代码很好地举例说明了 Kivy 中的 Popup 对话框。首先,我们在父窗口的垂直框布局中添加一个标签和一个按钮。按钮点击会弹出一个单列网格布局,其中包含一个文本输入,要求用户输入姓名。当弹出窗口关闭时,该文本用于更改父窗口的标签。

以下是完整代码 -

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.core.window import Window

Window.size = (720, 400)

class PopupExample(App):
   def build(self):
      self.layout = GridLayout(cols=1, padding=10)
      
      self.l1 = Label(
         text='enter your name', font_size=32,
         color=[.8, .6, .4, 1]
      )
      self.layout.add_widget(self.l1)
      
      self.button = Button(text="Click Here")
      self.layout.add_widget(self.button)
      self.button.bind(on_press=self.onButtonPress)
      return self.layout
   
   def onButtonPress(self, button):
      layout = GridLayout(cols=1, padding=10)
      
      popupLabel = Label(text="Enter name")
      self.t1 = TextInput()
      closeButton = Button(text="OK")
      
      layout.add_widget(popupLabel)
      layout.add_widget(self.t1)
      layout.add_widget(closeButton)
      
      self.popup = Popup(
         title='Hello', content=layout,
         auto_dismiss=False, size_hint=(None, None),
         size=(200, 200)
      )
      self.popup.open()
      closeButton.bind(on_press=self.on_close)
   
   def on_close(self, event):
      self.l1.text = 'Thanks ' + self.t1.text
      self.popup.dismiss()
PopupExample().run()

输出

App 窗口如下所示 -

Kivy Popup Dialog

Kivy - 开关

Kivy 框架中的 Switch 小部件类似于我们在家中用来打开或关闭灯泡或风扇的电开关。应用程序窗口上的开关可以通过将其 active 属性滑动到 True 或 False 来翻转。

Switch 类在“kivy.uix.switch: 模块中定义。

from kivy.uix.switch import Switch
switch = Switch(**kwargs)

放置在应用程序窗口上时,Switch 对象如下所示 -

Kivy Switch

Switch 类定义了一个名为 active 的布尔属性,该属性指示开关是打开/关闭。通常,此属性附加到回调函数,以便在值从 True 更改为 False 或反之亦然时调用所需的操作。

def callback(instance, value):
   if value:
      print('the switch is ON')
   else:
      print ('The switch is OFF')
switch = Switch()
switch.bind(active=callback)

示例

我们将在以下代码中使用 Switch 小部件来启动或停止音频播放。应用程序设计包含一个标签和一个放置在水平框布局中的开关。

Switch 的 active 属性绑定到 switched() 方法。打开时,加载 Sound 对象并调用其 play() 方法。另一方面,当翻转到关闭时,将调用 stop() 方法。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.core.audio import SoundLoader
from kivy.uix.switch import Switch
from kivy.core.window import Window

Window.size = (720, 250)

class switchdemoapp(App):
   def switched(self, instance, value):
      if value == True:
         self.sound = SoundLoader.load('sample.mp3')
         self.l1.text = 'Playing. . .'
         self.sound.play()
      else:
         self.sound.stop()
         self.l1.text = 'Switch ON to Play'
   def build(self):
      box = BoxLayout(orientation='horizontal')
      self.l1 = Label(
         text = 'Switch ON to Play',
         font_size = 32, color = [.8, .6, .4, 1]
      )
      box.add_widget(self.l1)
      switch = Switch()
      switch.bind(active = self.switched)
      box.add_widget(switch)
      return box

switchdemoapp().run()

输出

程序以标签开始,要求用户将开关滑动到打开。标签标题更改为“正在播放”消息。将开关滑动到关闭以停止播放音乐。

Kivy Switch On Off

Kivy - 旋转器

Kivy 框架中的 Spinner 控件是一种更传统的下拉控件类型,不同于 Kivy 的 DropDown 小部件。与 DropDown 相比,构建和使用 Spinner 小部件更加简单方便。

Kivy 的下拉菜单和小部件之间的主要区别在于,下拉菜单可能包含任何其他 Kivy 小部件,例如 Label、Button、Image 等;而 Spinner 只是一个字符串列表。

Spinner 类在“kivy.uix.spinner”模块中定义

from kivy.uix.spinner import Spinner
spin = Spinner(**kwargs)

Spinner 小部件显示一个与当前选定值对应的文本标题。Spinner 对象可以使用不同的属性作为关键字参数构建。但是,这两个属性很重要 -

  • text 属性是一个字符串,显示默认值。

  • values 属性是一个 ListProperty,包含所有可供选择的选项。

要构建一个简单的 Spinner,请使用以下代码片段 -

from kivy.base import runTouchApp
from kivy.uix.spinner import Spinner

spinner = Spinner(
   text='English',
   values=('English', 'French', 'German', 'Chinese')
)

Spinner 对象的 text 属性可以绑定到回调,以便在每次进行选择时调用适当的操作。

def value_changed(spinner, text):
   print(You selected', text, 'language')
   
spinner.bind(text=show_selected_value)

Spinner 类中的其他属性列在下面 -

  • dropdown_cls − 用于在按下 Spinner 时显示下拉列表的类。它是一个 ObjectProperty,默认为 DropDown。

  • is_open − 默认情况下,Spinner 未打开。设置为 True 以打开它。

  • option_cls − 用于在 Spinner 下显示的下拉列表中显示选项的类。该类的 text 属性将用于表示值。其 on_release 事件用于在按下/触摸时触发选项。

  • text_autoupdate − 这是一个 BooleanProperty。它指示 Spinner 的文本是否应使用 values 属性的第一个值自动更新。将其设置为 True 将导致 Spinner 在每次 values 更改时更新其 text 属性。

  • values − 用户可以选择的值。它必须是字符串列表。它是一个 ListProperty,默认为 []。

下面的代码组装了一个 Spinner,并与一个标签关联,以在水平框中显示选定的值。下方的水平框包含一个 TextInput 和一个 Button。目的是为该按钮提供一个回调,该回调将文本框中的字符串添加到 Spinner 值中

程序在 App 类中包含两个回调方法。一个用于显示 Spinner 中选定的值,另一个用于向 Spinner 添加新的语言。

添加新语言的回调函数 -

def addvalue(self, instance):
   self.spin1.values.append(self.t1.text)

我们将此方法绑定到“添加”按钮。

在标签上显示所选语言 -

def on_spinner_select(self, spinner, text):
   self.spinnerSelection.text = "Selected Language is: %s" %self.spin1.text

我们将此方法绑定到 Spinner 小部件的“text”属性。

示例

将以下代码保存并运行为“spiinerdemo.py”

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.spinner import Spinner
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.window import Window

Window.size = (720, 400)

class SpinnerExample(App):
   def addvalue(self, instance):
      self.spin1.values.append(self.t1.text)
      
   def build(self):
      layout = BoxLayout(orientation='vertical')
      lo1 = BoxLayout(orientation='horizontal')
      self.spin1 = Spinner(
         text="Python",
         values=("Python", "Java", "C++", "C", "C#", "PHP"),
         background_color=(0.784, 0.443, 0.216, 1),
         size_hint=(.5, .4), pos_hint={'top': 1}
      )
      lo1.add_widget(self.spin1)
      self.spinnerSelection = Label(
         text="Selected value in spinner is: %s" % self.spin1.text,
         pos_hint={'top': 1, 'x': .4}
      )
      lo1.add_widget(self.spinnerSelection)
      layout.add_widget(lo1)
      lo2 = BoxLayout(orientation='horizontal')
      lo2.add_widget(Label(text="Add Language"))
      self.t1 = TextInput()
      
      self.b1 = Button(text='add')
      lo2.add_widget(self.t1)
      lo2.add_widget(self.b1)
      layout.add_widget(lo2)
      self.spin1.bind(text=self.on_spinner_select)
      self.b1.bind(on_press=self.addvalue)
      return layout
      
   def on_spinner_select(self, spinner, text):
      self.spinnerSelection.text = "Selected value in spinner is: %s" % self.spin1.text
      print('The spinner', spinner, 'have text', text)

if __name__ == '__main__':
   SpinnerExample().run()

输出

左上角的按钮是 Spinner。单击时,语言列表会下拉。您可以进行选择。所选名称将显示在其右侧的标签上。

Kivy Spinner

要向列表中添加新语言,请在文本框中键入,然后单击“添加”按钮。Spinner 小部件将在底部附加新语言名称。如果列表足够长,您可以使用鼠标向下滚动。

Kivy - 分隔器

Kivy 中的 Splitter 小部件在其包含的任何其他小部件或布局周围放置一个可拖动的边界。您可以拖动边界以调整其包含的对象的大小。边界可以放置在包含小部件的顶部或底部,或左侧或右侧。

Splitter 类在“kivy.uix.splitter”模块中定义。

from kivy.uix.splitter import Splitter
split = Splitter(**kwargs)

配置边界放置所需的重要属性之一是“sizable_from”。它定义了指定从哪个方向调整小部件的大小。选项包括:left、right、top 或 bottom;默认为“left”。

边界中间有一个手柄。您可以使用此手柄或双击它来拖动边界。

Splitter 类的其他属性如下 -

  • border - 用于 BorderImage 图形指令的边框。这必须是一个包含四个值的列表:(bottom, right, top, left),默认为 [4,4,4,4]

  • keep_within_parent - 如果为 True,它将限制 Splitter 停留在其父小部件内。

  • max_size - 指定小部件不可调整大小的最大尺寸。max_size 默认为 500pt。

  • min_size - 指定小部件不可调整大小的最小尺寸。默认为 100 pt。

  • rescale_with_parent - 如果为 True,则在调整其大小时会自动更改大小以占据父小部件的相同比例,同时保持在 min_size 和 max_size 范围内。

  • sizable_from - 指定小部件是否可调整大小。选项包括 - left、right、top 或 bottom;默认为 left。

Kivy Splitter

示例

让我们创建一个简单的水平框布局,并在两个按钮之间放置一个 Image 小部件。但是,Image 对象放置在从左侧可调整大小的 Splitter 内部。

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.splitter import Splitter
from kivy.core.window import Window

Window.size = (720,350)

class splitterApp(App):
   def build(self):
      layout=BoxLayout(orientation='horizontal')
      b1=Button(
         text='Button1',
         font_size=24, color=(1,0,0,1)
      )
      layout.add_widget(b1)
      spl=Splitter(sizable_from = 'left')
      img=Image(source='Logo.jpg')
      spl.add_widget(img)
      layout.add_widget(spl)
      b2=Button(
         text='Button 2', font_size=24,
         background_color =(.8, .4, .3, 1)
      )
      layout.add_widget(b2)
      return layout
   
splitterApp().run()

输出

程序运行时,您将看到一个可拖动的边界,其手柄位于图像的左侧。拖动它以调整图像大小。

Kivy Splitter Resize Image

这是程序的“kv”脚本版本,用于演示从底部可调整大小的垂直 Splitter 的用法。

BoxLayout:
   orientation:'vertical'
   Button:
      text: 'Button 1'
      font_size:24
      color:(1,0,0,1)
   Splitter:
      sizable_from : 'bottom'
      Image:
         source:'Logo.jpg'
   Button:
      text:'Button 2'
      font_size:24
      background_color: (.8, .4, .3, 1)

垂直可调整大小的 Image 小部件显示如下 -

Kivy Splitter Vertical

Kivy - 进度条

当 GUI 应用程序正在执行一些耗时的过程时,应该有一些机制让用户知道其进度。Kivy 框架中的 Progressbar 小部件显示正在进行的任务的进度可视化表示。

Kivy 的进度条小部件仅支持水平模式,并在应用程序窗口上以增量进度显示。

Progressbar 类在“kivy.uix.progressbar”模块中定义。

from kivy.uix.progressbar import ProgressBar
pb = Progressbar(**kwargs)

Progressbar 小部件是仅显示的小部件,没有任何交互元素,因为它不会引发或传播任何事件。

要创建 Progressbar 的实例,您需要设置其 max 属性。

from kivy.uix.progressbar import ProgressBar
pb = ProgressBar(max=1000)

该小部件具有 value 属性。您可以将其分配给小于其“max”属性的任何数字。

pb.value=100

但是,由于 progressbar 类没有任何事件和事件处理程序,因此我们需要手动将 value 属性链接到某个进程。

这可以通过定期读取渐进任务属性的瞬时值并更新进度条的 value 属性来完成。

假设我们有一个长的 for 循环,它在每隔一秒钟后进行下一次迭代。

for i in range(1000):
   time.delay(1)
   print (i)

我们希望在进度条上显示 I 的增量。因此,我们安排了一个时钟事件,该事件在每隔一秒钟后调用一个函数。

progev = Clock.schedule_interval(update_progress, 1.0)

每秒钟后,update_progesss() 函数将进度条的 value 属性更新为迭代计数器 i

def update_progress():
   pb.value = i

示例

让我们在以下代码中实现此方法。GUI 设计包含两个按钮和一个标签。按下开始按钮时,它会将 mp3 文件加载到 Sound 对象中。声音文件的长度用作进度条的 max 属性

self.sound = SoundLoader.load('sample.mp3')
self.length = self.sound.length

当调用 play() 方法时,我们按上述方式安排进度事件。

self.prog_ev = Clock.schedule_interval(self.update_progress, 1.0)

update_progress() 方法读取 Sound 对象的 pos 属性并使用它来更新进度条的 value 属性

def update_progress(self, dt):
   if self.sound.state=='play':
      if self.prg.value < self.length:
         self.progress += 1 # Update value.
         self.prg.value=self.progress

以下是完整代码 -

from kivy.app import App
from kivy.clock import Clock
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.core.audio import SoundLoader
from kivy.uix.progressbar import ProgressBar
from kivy.properties import NumericProperty
from kivy.core.window import Window

Window.size = (720, 400)

class audiodemoapp(App):
   length = NumericProperty(0.0)
   progress = NumericProperty(0.0)

   def build(self):
      layout = GridLayout(cols=1, padding=10)
      self.prg = ProgressBar()
      layout.add_widget(self.prg)
      self.l1 = Label(
         text='Press Start to Play', font_size=40,
         color=[.8, .6, .4, 1]
      )
      layout.add_widget(self.l1)
      box = BoxLayout(orientation='horizontal')
      self.button1 = Button(text="Start", font_size=32)
      self.button2 = Button(
         text='Pause', font_size=32,
         disabled=True
      )
      box.add_widget(self.button1)
      box.add_widget(self.button2)
      layout.add_widget(box)
      self.button1.bind(on_press=self.start_stop)
      self.button2.bind(on_press=self.pause_resume)
      
      return layout

   def start_stop(self, event):
      if self.button1.text == 'Start':
         self.l1.text = 'Playing'
         self.button1.text = 'Stop'
         self.sound = SoundLoader.load('sample.mp3')
         self.length = self.sound.length
         self.pos = 0
         self.button2.disabled = False
         self.sound.play()
         self.prog_ev = Clock.schedule_interval(self.update_progress, 1.0)
      else:
         if self.button1.text == 'Stop':
            self.l1.text = 'Press Start to Play'
            self.sound.state = 'stop'
            self.button1.text = 'Start'
            self.sound.unload()
            self.button2.disabled = True
            self.pos = 0
   
   def pause_resume(self, event):
      if self.button2.text == 'Pause':
         self.button2.text = 'Resume'
         self.l1.text == 'Paused'
         self.pos = self.sound.get_pos()
         self.sound.stop()
      else:
         if self.button2.text == 'Resume':
            self.l1.text = 'Playing'
            self.button2.text = 'Pause'
            print(self.pos)
            self.sound.seek(self.pos)
            self.sound.play()
            self.prog_ev = Clock.schedule_interval(self.update_progress, 1.0)
   def update_progress(self, dt):
      if self.sound.state == 'play':
         if self.prg.value < self.length:
            self.progress += 1 # Update value
            self.prg.value = self.progress
         else: # End case.
            self.progress = 0 # Reset value
            self.prog_ev.cancel() # Stop updating
   
audiodemoapp().run()

输出

运行以上代码。按下开始按钮。顶部的进度条显示音乐文件的瞬时播放位置。

Kivy Progress Bar

Kivy - 气泡

Kivy 框架包含一个 Bubble 小部件,它充当一个小弹出菜单,在其内容的任何一侧都有一个箭头。箭头的方向可以根据需要配置。您可以通过设置其“arrow_pos”属性的相对位置来放置它。

气泡的内容放置在“BubbleContent”对象中,它是 BoxLayout 的子类。可以水平或垂直放置一个或多个 BubbleButtons。虽然建议使用 BubbleButtons,但您可以在气泡的内容中添加任何小部件。

Kivy Bubble

Bubble、BubbleContent 和 BubbleButton 类在 kivy.uix.bubble 模块中定义。

from from kivy.uix.bubble import Bubble

Bubble 类的以下属性有助于自定义 Bubble 菜单的外观和行为 -

  • arrow_color - 箭头颜色,格式为 (r, g, b, a)。要使用它,您必须首先设置 arrow_image,默认为 [1, 1, 1, 1]。

  • arrow_image - 指向气泡的箭头的图像。

  • arrow_margin - 自动计算的边距,箭头小部件以像素为单位占据 x 和 y 方向。

  • arrow_pos - 指定箭头的相对位置,其中之一为预定义值:left_top、left_mid、left_bottom top_left、top_mid、top_right right_top、right_mid、right_bottom bottom_left、bottom_mid、bottom_right。默认值为“bottom_mid”。

  • content - 这是保存气泡主要内容的对象。

  • show_arrow - 指示是否显示箭头。默认为 True。

  • BubbleButton - 旨在用于 BubbleContent 小部件的按钮。您可以使用“普通”按钮代替它,但除非更改背景,否则它可能看起来不错。

  • BubbleContent - 可用作 Bubble 内容小部件的样式化的 BoxLayout。

以下示意图“kv”语言脚本用于构建一个简单的 Bubble 对象 -

Bubble:
   BubbleContent:
      BubbleButton:
         text: 'Button 1'
      BubbleButton:
         text: 'Button 2'

就像普通按钮一样,我们可以将 BubbleButton 绑定到其“on_press”事件的回调函数。

示例

执行以下代码时,它会显示一个普通按钮。单击时,会弹出一个包含三个 BubbleButtons 的 Bubble 菜单。这些 BubbleButtons 中的每一个都会调用一个 pressed() 回调方法,该方法读取按钮的标题并在控制台上打印它。

我们使用以下“kv”语言脚本来组装 Bubble 菜单。已定义一个名为“Choices”的类,它继承自“kivy.uix.bubble.Bubble”类。

class Choices(Bubble):
   def pressed(self, obj):
      print ("I like ", obj.text)
      self.clear_widgets()

该类具有 pressed() 实例方法,由每个 BubbleButton 调用。

以下是“kv”脚本 -

<Choices>
   size_hint: (None, None)
   size: (300, 150)
   pos_hint: {'center_x': .5, 'y': .6}
   canvas:
      Color:
         rgb: (1,0,0)
      Rectangle:
         pos:self.pos
         size:self.size
   BubbleContent:
      BubbleButton:
         text: 'Cricket'
         size_hint_y: 1
         on_press:root.pressed(self)
      BubbleButton:
         text: 'Tennis'
         size_hint_y: 1
         on_press:root.pressed(self)
      BubbleButton:
         text: 'Hockey'
         size_hint_y: 1
         on_press:root.pressed(self)

BoxLayout 小部件充当主应用程序窗口的根小部件,并包含一个按钮和一个标签。“on_press”事件调用“show_bubble()”方法,该方法会弹出气泡。

class BubbleTest(FloatLayout):
   def __init__(self, **temp):
      super(BubbleTestApp, self).__init__(**temp)
      self.bubble_button = Button(
         text ='Your favourite Sport',
         pos_hint={'center_x':.5, 'center_y':.5},
         size_hint=(.3, .1),size=(300, 100)
      )
      self.bubble_button.bind(on_release = self.show_bubble)
      self.add_widget(self.bubble_button)

   def show_bubble(self, *arg):
      self.obj_bub = Choices()
      self.add_widget(self.obj_bub)

驱动程序 App 类代码如下 -

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.bubble import Bubble
from kivy.properties import ObjectProperty
from kivy.core.window import Window

Window.size = (720,400)

class MybubbleApp(App):
   def build(self):
      return BubbleTest()

MybubbleApp().run()

输出

该应用程序在主窗口的中心显示一个按钮。单击时,您应该在它上面看到 Bubble 菜单。

Kivy Bubble Menu

每次单击气泡中的任何选项时,控制台都会显示结果并隐藏气泡。

I like Tennis
I like Hockey

Kivy - 标签页面板

许多 GUI 工具包都包含选项卡面板,因为它非常方便地将界面控件分组显示,而不是一个大型表单,远远超出了显示设备的尺寸。Kivy 中的 TabbedPanel 小部件使能够在不同的面板中显示小部件或布局,而不会使 GUI 设计看起来笨拙。不同面板中的控件可以在它们之间共享数据。

不同的选项卡显示为顶部的菜单,其中包含实际选项卡按钮的标题区域和显示当前选项卡内容的内容区域。

Kivy Tabbed Panel

TabbedPanel 对象是用于一个或多个面板的顶级容器。对应于每个面板,都会添加一个 TabbedPanelItem 对象。每个 TabbedPAnelItem 又可以容纳任何一个小部件或包含多个小部件(例如 GridLayout 或 BoxLayout 等)的布局

这两个类都在 kivy.uix.tabbedpanel 模块中定义。

from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem

构建选项卡面板的示意图语句流可能如下 -

main=TabbedPanel()
tab1=TabbedPanelItem(text='Tab 1')
Label=Label(text='Label')
tab1.add_widget(label)
tab2=TabbedPanelItem(text='Tab 2')
btn=Button(text="Button")
tab2.add_widget(btn)
main.add_widget(tab1)
main.add_widget(tab2)

可以通过调整一些属性来进一步自定义选项卡面板 -

您可以选择显示选项卡的位置,方法是将tab_pos属性设置为以下值之一:left_top、left_mid、left_bottom、top_left、top_mid、top_right、right_top、right_mid、right_bottom、bottom_left、bottom_mid、bottom_right。

  • 每个选项卡都有一个特殊的按钮TabbedPAnelHeader,其中包含一个 content 属性。

  • 选项卡面板带有一个默认选项卡,您可以通过将do_default_tab设置为 False 来将其删除。

如果显示默认选项卡,则会提供on_default_tab事件以关联回调函数 -

tp.bind(default_tab = my_default_tab_callback)

可以通过多种方式删除选项卡和内容 -

  • tp.remove_widget()删除选项卡及其内容。

  • tp.clear_widgets()清除内容区域中的所有小部件。

  • tp.clear_tabs()删除 TabbedPanelHeaders

示例

在下面的示例中,我们使用两个选项卡面板,第一个用于显示简单的注册表单,第二个用于显示登录表单。

我们将使用“kv”语言脚本构建设计。

  • 默认选项卡已删除。

  • 第一个选项卡包含一个 2 列网格布局,并包含标签和文本输入框,供用户输入其详细信息,然后是“提交”按钮。

  • 第二个选项卡也包含一个两列网格,允许已注册用户输入电子邮件和密码。

TabbedPanel:
   size_hint: .8, .8
   pos_hint: {'center_x': .5, 'center_y': .5}
   do_default_tab: False
   TabbedPanelItem:
      text:"Register Tab"
      GridLayout:
         cols:2
         Label:
            text:"Name"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.75}

         TextInput:
            size_hint:(.4, .1)
            pos_hint:{'x':.3, 'y':.65}

         Label:
            text:"email"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.55}

         TextInput:
            size_hint:(.4, .1)
            pos_hint:{'x':.3, 'y':.45}

         Label:
            text:"Password"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.35}

         TextInput:
            password:True
            size_hint:(.4, .1)
            pos:(400, 150)
            pos_hint:{'x':.3, 'y':.25}

         Button:
            text:'Submit'
            size_hint : (.2, .1)
            pos_hint : {'center_x':.5, 'center_y':.09}

   TabbedPanelItem:
      text:'Login Tab'
      GridLayout:
         cols:2
      
      Label:
         text:"email"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.55}
      
      TextInput:
         size_hint:(.4, .1)
         pos_hint:{'x':.3, 'y':.45}
      
      Label:
         text:"Password"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.35}
      
      TextInput:
         password:True
         size_hint:(.4, .1)
         pos:(400, 150)
         pos_hint:{'x':.3, 'y':.25}

      Button:
         text:'Submit'
         size_hint : (.2, .1)
         pos_hint : {'center_x':.5, 'center_y':.09}

利用以上“kv”脚本设计的 App 代码如下 -

from kivy.app import App
from kivy.core.window import Window

Window.size = (720,300)

class TabDemoApp(App):
   def build(self):
      pass
      
TabDemoApp().run()

输出

运行以上代码时,应用程序窗口会显示选项卡面板,并显示第一个选项卡的内容。单击“登录”选项卡以查看第二个选项卡的内容。

Kivy Login Tab

Kivy - 散点图

Kivy 中的 Scatter 小部件对于多点触控设备特别有用,在其中它用于旋转和缩放应用程序窗口的内容。

Scatter 小部件通过在绘制子级之前更改模型视图矩阵来执行矩阵变换,并在绘制完成后恢复先前的矩阵,以便可以在整个子级树上执行旋转、缩放和平移,而无需更改任何小部件属性。

默认情况下,Scatter 控件没有图形表示:它只是一个容器。其他控件会添加到 Scatter 对象中。但是,需要注意的是,Scatter 控件不是布局。您必须自己管理子控件的大小。它们相对于 Scatter 定位,类似于 RelativeLayout。这就是为什么拖动 Scatter 不会改变子控件的位置,只有 Scatter 的位置会改变。Scatter 的大小不会影响其子控件的大小。

Scatter 类定义在 "kivy.uix.scatter" 模块中。Scatter 控件的基本用法如下:

from kivy.uix.scatter import Scatter
scatter=Scatter.add_widget(Image(source='logo.jpg'))

Scatter 对象创建时,默认情况下所有交互都启用。但是,您可能希望自定义交互,为此需要相应地定义 Scatter 对象的属性。

  • auto_bring_to_front − 如果为 True,则该控件将在绘制时自动推到父控件列表的顶部。

  • do_rotation − 允许旋转。默认情况下,此属性为 True。

  • do_scale − 允许缩放。默认为 True。

  • do_translation − 允许在 X 或 Y 轴上平移。

  • scale − Scatter 的缩放值。scale 是一个 AliasProperty,默认为 1.0。

  • scale_max − 允许的最大缩放因子。允许的最大缩放因子的默认值为 1e20。

  • scale_min − 允许的最小缩放因子,默认值为 0.01。

示例 1

这是一个关于 Scatter 控件如何工作的简单示例。我们只是在 Kivy 应用中的 Scatter 控件中添加了一个标签。应用程序启动时,标签文本显示在应用程序窗口的左下角。使用鼠标将其拖动到窗口表面上的任意位置。

为了在普通桌面电脑上模拟多点触控操作,请通过鼠标右键在标签区域创建两个标记,然后您可以通过拖动这两个标记来放大或旋转标签。

如果您使用的是多点触控设备,则可以通过双指触控执行缩放和旋转。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.core.window import Window

Window.size = (720,350)

class scatterdemoapp(App):
   def build(self):
      box=BoxLayout(orientation='vertical')
      scatr=Scatter()
      lbl=Label(text="Hello", font_size=60)
      scatr.add_widget(lbl)
      box.add_widget(scatr)
      return box

scatterdemoapp().run()

输出

让我们检查一下输出是什么样子的:

kivy scatter

示例 2

或者,也可以使用 "kv" 语言脚本构建相同的界面。

BoxLayout:
   orientation:'vertical'
   Scatter:
      Label:
         text:"Hello"
         font_size:60

让我们为上面的示例添加更多交互性。在这里,我们在 Scatter 控件上方,垂直布局中添加了一个文本输入框。

"text" 属性绑定到标签的 text 属性。因此,当您在文本框中添加/删除字母时,标签文本会更新。仍然可以执行所有 Scatter 操作,如旋转和缩放。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.core.window import Window

Window.size = (720,300)

class scatterdemoapp(App):
   def build(self):
      box = BoxLayout(orientation='vertical')
      text1 = TextInput(
         text='Hello World', height=100,
         size_hint=(Window.width, None)
      )
      box.add_widget(text1)
      scatr = Scatter()
      self.lbl = Label(text="Hello", font_size=60)
   
      text1.bind(text=self.lbl.setter('text'))
      scatr.add_widget(self.lbl)
      box.add_widget(scatr)
      return box
   
scatterdemoapp().run()

输出

现在让我们检查一下输出窗口是什么样子的:

Kivy Scatter Hello

Kivy - 手风琴

这个 GUI 控件,因为它与同名的乐器相似,所以被称为 "手风琴"。在 Kivy 中,手风琴是一个图形控件元素,包含一个垂直或水平堆叠的项目列表,例如标签、按钮或图像。

就像在手风琴中,可以通过向外拉伸来扩展风箱的部分一样,每个项目都可以“展开”或“折叠”以显示与该项目关联的内容。可以有零个展开的项目、恰好一个项目或多个项目同时展开,具体取决于配置。

手风琴在用途上类似于选项卡面板,一个项目列表,其中恰好有一个项目展开成一个面板。

"kivy.uix.accordion" 模块中有两个重要的类 - "Accordion" 和 "AccordionItem"。每个 AccordianItem 对象都包含任何一个 Kivy 控件,例如标签、按钮、图像,甚至其他布局对象。然后将多个 AccordioItems 添加到主 Accordion 对象中。

Accordion 类支持以下属性/方法:

  • title − AccordionItem 的标题字符串。

  • min_space − 为每个项目的标题使用的最小空间。每次发生布局事件时,此值都会自动为每个子项设置。它是一个 NumericProperty,默认为 44(像素)。

  • orientation − 手风琴布局的方向。可以是垂直或水平。

  • collapse − 布尔属性,指示当前项目是否已折叠。

示例 1

在以下代码中,Accordion 对象用作应用程序窗口的根控件。标签、按钮和图像控件分别添加到其每个 AccordionItem 控件中。

完整的代码如下:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.core.window import Window

Window.size = (720,400)

class accordiandemoapp(App):
   def build(self):
      acc=Accordion(orientation='horizontal')
      item1=AccordionItem(title='Text Panel')
      item1.add_widget(Label(text='Hello World'))
      
      item2=AccordionItem(title='Button Panel')
      self.btn=Button(text='ok')
      item2.add_widget(self.btn)
      
      item3=AccordionItem(title='Image Panel')
      img = Image()
      img.source='kivy-logo.png'
      
      item3.add_widget(img)
      acc.add_widget(item1)
      acc.add_widget(item2)
      acc.add_widget(item3)
      
      return acc
   
accordiandemoapp().run()

输出

运行以上程序并点击“图片面板”,以显示其内容,即 Kivy 的 logo。

Kivy Accordion

您可以通过点击其他面板来进一步展开它们。

示例 2(使用“kv”脚本)

我们删除 build() 方法中的所有语句,将其替换为 pass 语句,然后将以下脚本保存为 accordiondemo.kv 文件。这次,Accordion 的默认方向更改为垂直方向。

BoxLayout:
   orientation: "vertical"
   size: root.width, root.height
   
   Accordion:
      orientation: 'vertical'

      AccordionItem:
         title: "Button Panel"
         Button:
            text: 'OK'
            
      AccordionItem:
         title: "Text Panel"
         Label:
            text: "Hello World"
            font_size: 32
      AccordionItem:
         title: "Image Panel"
         Image:
            source: 'kivy-logo.png'

输出

程序运行后,按钮面板展开:

Kivy Accordion Button Panel

Kivy - 文件选择器

在 GUI 应用程序中,您通常需要从本地文件系统中选择所需的文件。Kivy 框架提供了 "kivy.uix.filechooser" 模块,该模块提供了用于描述、显示和浏览文件系统的各种类。

filechooser 模块中的类采用 MVC 设计。它们可以分类如下:

  • 模型FileSystemAbstract 类的具体实现表示,例如 FileSystemLocal

  • 视图FileChooserListLayoutFileChooserIconLayout 类表示。这些分别由 FileChooserListViewFileChooserIconView 控件使用。

  • 控制器FileChooserController 的具体实现表示,即 FileChooser、FileChooserIconViewFileChooserListView 类。

FileChooserIconView 和 FileChooserListView 类提供了非常易于使用的控件,它们提供了两种不同的视觉表示形式来访问文件系统,顾名思义。

FileChooserListView 控件以垂直列表中文本项的形式显示文件和文件夹。文件夹在其名称左侧用“>”符号标识,单击可展开或折叠其文件和子文件夹。

Kivy File Chooser

FileChooserIconView 控件显示一个文件夹图标,其名称位于下方,以及一个文件图标及其名称。

kivy FileChooserIconView

如果文件/文件夹数量超过控件的高度和宽度,则垂直和水平滚动条将附加到它。

FileChooser 视图具有以下属性:

  • files − 应用过滤器后,路径指定的目录中的文件列表。files 是一个只读 ListProperty。

  • filter_dirs − 指示过滤器是否也应应用于目录。filter_dirs 是一个 BooleanProperty,默认为 False。

  • filters − filters 指定要应用于目录中文件的过滤器。filters 是一个 ListProperty,默认为 []。如果为空,则等效于 "*",表示没有文件从列表中过滤掉。当路径更改时,过滤器不会重置。如果需要,您需要自己执行此操作。

您可以在列表中指定以下一种或多种模式:

序号 模式列表和描述
1 *

匹配所有内容

2 ?

匹配任何单个字符

3 [seq]

匹配 seq 中的任何字符

4 [!seq]

匹配 seq 中不存在的任何字符

这两个视图都会引发 on_selection 事件,可以将回调绑定到该事件。当生成此事件时,如果选择了文件夹,它将展开或折叠它。如果选择了文件,则其名称将传递给回调。

ListView 示例

在第一个示例中,我们使用 FileChhoserListView 控件和标签在垂直框布局中构建应用程序窗口。

为此,请使用以下“kv”文件脚本。select() 方法绑定到视图的“on_selection”事件。

<Filechooser>:
   label: label
   orientation: 'vertical'
   BoxLayout:
      FileChooserListView:
         canvas.before:
            Color:
               rgb: .4, .5, .5
            Rectangle:
               pos: self.pos
               size: self.size
         on_selection: root.select(*args)

   Label:
      id: label
      size_hint_y: .1
      canvas.before:
         Color:
            rgb: .5, .5, .4
         Rectangle:
            pos: self.pos
            size: self.size

请注意 FileChooseListView 和 Label 控件的 canvas 对象的使用,以提供背景颜色。

Kivy 应用程序类代码如下:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window

Window.size = (720,400)

class Filechooser(BoxLayout):
   def select(self, *args):
      try: self.label.text = args[1][0]
      except: pass
      
class FileDemoApp(App):
   def build(self):
      return Filechooser()
if __name__ == '__main__':
   FileDemoApp().run()

输出

运行以上代码后,您将获得文件/文件夹列表。标签上将显示文件的名称及其完整路径。

Kivy File Full Path

IconView 示例

我们现在演示 FileChooserIconView 的用法。它放置在一个水平框中,旁边有一个图像控件。虽然 FileChooserIconView 的大多数配置与上一个示例相同,但我们添加了 filters 属性以将文件的显示限制为仅 png 文件。

<Filechooser>:
   img: img
   orientation: 'horizontal'
   BoxLayout:
      FileChooserIconView:
      filters: ['*.png']
         canvas.before:
            Color:
               rgb: .5, .4, .5
            Rectangle:
               pos: self.pos
               size: self.size
         on_selection: root.select(*args)
   
   Image:
      id: img
      source:""

由于我们打算显示选定的图像,因此更改 select() 方法,以便将 Image 对象的 source 属性分配给选定的文件。

class Filechooser(BoxLayout):
   def select(self, *args):
      try:
         self.img.source = args[1][0]
      except:
         print ('error')
         
class FileIconApp(App):
   def build(self):
      return Filechooser()

# run the App
if __name__ == '__main__':
   FileIconApp().run()

输出

运行程序并浏览到所需的图像文件。选定的图像将显示在右侧。

Kivy File Chooser Image

Kivy - 颜色选择器

Kivy 的 ColorPicker 控件是一个内置的对话框,它允许您以多种方式选择颜色。它提供了一个色轮,从中可以选择所需的颜色。它还具有滑块控件,可以调整以获得所需的颜色值。滑块也可用于 HSV 方案中的透明度和颜色值。

每个颜色属性都有一个文本框,您可以在其中直接输入 0 到 255 之间的数字颜色值。

ColorPicker 控件如下所示:

Kivy Color Wheel

ColorPicker 类定义在 "kivy.uix.colorpicker" 模块中。

from kivy.uix.colorpicker import ColorPicker
colour = ColorPicker(**kwargs)

要呈现上述颜色对话框,只需将 ColorPicker 对象添加到父窗口即可。如果其 color 属性绑定到事件处理程序,则颜色值可用于进一步处理,例如使用所选颜色更改某个对象的颜色。

除了颜色之外,ColorPicker 对象还具有 hsv 和 hex_color 属性。在以下代码片段中,当选择颜色时,颜色、hsv 和 hex_color 值将打印到控制台。

对于从色轮中选择的颜色,RGB、HSV、A 和十六进制值将显示在文本框中,并由滑块位置指示。

Kivy Color Picker

回调方法将值打印到控制台:

RGBA = [1, 0.5, 0.5, 1]
HSV = (0.0, 0.5, 1)
HEX = #ff7f7fff

ColorPicker 属性

  • r − 当前选择的颜色的红色值。它是一个 BoundedNumericProperty,可以是 0 到 1 之间的值。默认为 0。

  • g − 当前选择的颜色的绿色值。"g" 是一个 BoundedNumericProperty,可以是 0 到 1 之间的值。

  • b − 当前选择的颜色的蓝色值。"b" 是一个 BoundedNumericProperty,可以是 0 到 1 之间的值。

  • a − 当前选择的颜色的 Alpha 值。"a" 是一个 BoundedNumericProperty,可以是 0 到 1 之间的值。

  • hsv − hsv 以 hsv 格式保存当前选择的颜色。hsv 是一个 ListProperty,默认为 (1, 1, 1)。

  • hex_color − hex_color 以十六进制格式保存当前选择的颜色。hex_color 是一个 AliasProperty,默认为 #ffffffff。

  • color − color 以 rgba 格式保存当前选择的颜色。color 是一个 ListProperty,默认为 (1, 1, 1, 1)。

  • font_name − 指定 ColorPicker 上使用的字体。font_name 是一个 StringProperty。

  • wheel − wheel 保存色轮。wheel 是一个 ObjectProperty,默认为 None。

示例

让我们使用 ColorPicker 控件选择任何所需的颜色,无论是从色轮还是从滑块,或者通过直接输入颜色值;并将其应用于父窗口上的标签。

Kivy 应用程序窗口包含一个标签和一个按钮,放置在网格布局中。在按钮的 on_press 事件上,将显示一个弹出窗口。

弹出窗口使用 ColorPicker 和一个按钮,并以另一个网格布局进行设计。弹出窗口按钮在关闭弹出窗口之前,将所选颜色应用于应用程序窗口上的标签。

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.colorpicker import ColorPicker
from kivy.core.window import Window

Window.size = (720, 350)

class ColorPickApp(App):
   def build(self):
      self.layout = GridLayout(cols=1, padding=10)
      self.l1 = Label(
         text='www.tutorialspoint.com',
         font_size=32, color=[.8, .6, .4, 1]
      )
      self.layout.add_widget(self.l1)
      self.button = Button(text="Click Here")
      self.layout.add_widget(self.button)
      self.button.bind(on_press=self.onButtonPress)
      return self.layout
      
   def onButtonPress(self, button):
      layout = GridLayout(cols=1, padding=10)
   
      self.clr = ColorPicker()
      closeButton = Button(text="OK", size_hint=(.1, .05))
      
      layout.add_widget(self.clr)
      layout.add_widget(closeButton)
   
      self.popup = Popup(
         title='Hello', content=layout, auto_dismiss=False
      )
      self.popup.open()
   
      closeButton.bind(on_press=self.on_close)
   def on_close(self, event):
      self.l1.color = self.clr.hex_color
      self.popup.dismiss()
      
ColorPickApp().run()

输出

此 Kivy 应用程序的初始显示将显示一个标签和一个按钮。当您点击按钮时,ColorPicker 小部件将弹出。

选择所需的颜色并按确定。您将看到标签文本相应地更改其颜色。

Kivy Color Picker Select

Kivy - 代码输入

Kivy 框架中的 CodeInput 小部件是一个专门的 TextInput 框,能够显示可编辑的文本,并根据所选语言词法分析器的语法进行突出显示。

CodeInput 小部件需要安装 **pygments** 包。

  • pygments 包是一个 Python 语法高亮器。

  • 它用于需要美化源代码的应用程序。

  • Pygments 支持几乎所有语言,包括编程语言、脚本语言,甚至能够根据框架和库语法提供语法高亮。

  • 支持以 Lexer 类的形式提供。例如,要选择 Python 语法以突出显示语言元素,我们需要导入 Python3Lexer,对于 C++ 代码,需要导入 CppLexer 类。具体来说,Kivy 代码使用 KivyLexer 进行高亮显示。

如果您的当前工作环境未安装 pygments,请运行以下命令:

pip3 install pygments

CodeInput 类定义在 "kivy.uix.codeinput" 模块中。

from kivy.uix.codeinput import CodeInput
codewidget = CodeInput(**kwargs)

CodeInput 类继承 TextInput 类,以及 CodeNavigationBehaviou 混合类。就像 TextInput 对象一样,CodeInput 小部件是一个多行文本框,可以添加到其他布局类中。可以通过指定以下属性来实例化它:

  • **lexer** - 存储 pygments 用于突出显示代码的所选 Lexer。它是一个 ObjectProperty,默认为 PythonLexer。

  • **style** - 用于格式化的 pygments 样式对象。当设置 style_name 时,它将更改为相应的样式对象。

  • **style_name** - 用于格式化的 pygments 样式的名称。style_name 是一个 OptionProperty,默认为 'default'。pygments 中提供了许多样式。一些示例包括 emacs、xcode、vs、bw、colorful 等等。

除了这些之外,属性也继承自 TextInput 类。CodeNavigationBehavior 混合类修改了 TextInput 中的导航行为,使其像 IDE 而不是文字处理器一样工作。

示例

在以下代码中,CodeInput 小部件使用 PythonLexer 对 Python 源代码执行语法高亮。使用 Python 的文件对象读取 "code.py" 文件,并将数据分配给 CodeInput 对象的 text 属性。

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.codeinput import CodeInput
from pygments.lexers.python import Python3Lexer
from kivy.uix.scrollview import ScrollView
from kivy.uix.codeinput import CodeInput
from kivy.core.window import Window

Window.size = (720,400)

class codeinputdemoapp(App):
   def build(self):
      scr = ScrollView(size=Window.size)
      codinp = CodeInput(style='emacs')
      codinp.height = max(codinp.minimum_height, scr.height)
      file=open('code.py')
      text=file.read()
      codinp.text=text
      scr.add_widget(codinp)
      return scrx

codeinputdemoapp().run()
Kivy Code Input

要突出显示使用 Kivy 相关关键字和函数的代码,请加载 KivyLexer。您还可以尝试使用 "pigment" 样式,将其更改为 "colorful" 或任何其他可用的样式。

from kivy.extras.highlight import KivyLexer

读取 "kivycode.py" 文件并将其加载到 CodeInput 框中。

file=open('kivycode.py')
text=file.read()
codinp.text=text
Kivy Code Input File

您也可以尝试使用 CppLexer 显示 C++ 代码:

from pygments.lexers.c_cpp import CppLexer

这次,将样式更改为 "friendly"。

file=open('test.cpp')
text=file.read()
codinp.text=text
kivy code CppLexer

Kivy - 模态视图

Kivy 框架中的 ModalView 小部件用于在父窗口顶部弹出对话框。ModalView 小部件的行为类似于 Kivy 的 Popup 小部件。实际上,Popup 类本身是 ModalView 类的子类,具有一些额外的功能,例如具有标题和分隔符。

默认情况下,Modalview 的大小等于“主”窗口的大小。小部件的 size_hint 默认值为 (1, 1)。如果您不希望视图全屏显示,请使用小于 1 的 size_hint 值(例如 size_hint=(.8, .8)),或者将 size_hint 设置为 None 并使用固定大小属性。

ModalView 类定义在 kivy.uix.modalview import ModalView 模块中。以下语句将生成一个 ModalView 对话框:

from kivy.uix.modalview import ModalView
view = ModalView(size_hint=(None, None), size=(300, 200))
view.add_widget(Label(text='ModalView Dialog'))

与 Popup 一样,当您单击 ModalView 对话框外部时,它将被关闭。要阻止此行为,请将 auto-dismiss 设置为 False。

要使 ModalView 对话框出现,您需要调用其 open() 方法。

view.open()

如果 auto_dismiss 为 False,则需要调用其 dismiss() 方法将其关闭。

view.dismiss()

通常,open() 和 dismiss() 方法都将在某个事件(例如按钮的 on_press 事件)上调用。通常在单击父窗口上的按钮时打开 ModalView,并在单击 ModalView 布局中的按钮时关闭它。

ModalView 事件

ModalView 在视图打开和关闭之前以及视图打开和关闭时发出事件。

  • **on_pre_open** - 在 ModalView 打开之前触发。当此事件触发时,ModalView 尚未添加到窗口中。

  • **on_open** - ModalView 打开时触发。

  • **on_pre_dismiss** - 在 ModalView 关闭之前触发。

  • **on_dismiss** - ModalView 关闭时触发。如果回调返回 True,则关闭操作将被取消。

示例

以下代码提供了一个易于实现的 ModalView 对话框示例。主应用程序窗口显示一个标题为“单击此处”的按钮。单击时,将弹出一个模态对话框。

ModalView 通过将标签和按钮放在网格布局中来构建。在弹出窗口中,我们有一个标题为“确定”的按钮。它的 on_press 事件绑定到 ModalView 对象的 dismiss() 方法,导致它消失。

下面给出了该示例的完整代码:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.modalview import ModalView
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720, 300)

class ModalViewDemoApp(App):
   def showmodal(self, obj):
      view = ModalView(
         auto_dismiss=False, size_hint=(None, None),
         size=(400, 100)
      )
      grid = GridLayout(cols=1, padding=10)
      grid.add_widget(Label(
         text='ModalView Popup', font_size=32,
         color=[1, 0, 0, 1]
      ))
      btn = Button(
         text='ok', font_size=32,
         on_press=view.dismiss
      )
      grid.add_widget(btn)
      view.add_widget(grid)
      view.open()

   def build(self):
      btn = Button(
         text='click here', font_size=32,
         on_press=self.showmodal,
         pos_hint={'center_x': .5, 'center_y': .1},
         size_hint=(.3, None), size=(200, 100)
      )
      return btn
ModalViewDemoApp().run()

输出

当单击主应用程序窗口上的按钮时,ModalView 对话框将弹出。

kivy modal view

Kivy - 切换按钮

Kivy 框架中的 ToggleButton 小部件的行为有点像 Checkbox 小部件。它还具有一个名为 state 的二进制属性,该属性有两个可能的值:**normal** 或 **down**。

ToggleButton 是一个按钮,不同之处在于,当按下按钮时,按下和释放事件几乎同时发生;而当按下切换按钮时,down 状态将持续存在,直到再次按下将其恢复到 normal 状态。

Kivy Toggle Button

ToggleButton 的两种状态分别具有不同的背景颜色,可以通过为 "background_normal" 和 "background_down" 属性赋值来自定义。

ToggleButton 类继承 Button 类和 ToggleButtonBehavior 混合类。它定义在 kivy.uix.togglebutton 模块中。

from kivy.uix.togglebutton import ToggleButton
toggle = ToggleButton(**kwargs)

就像 Checkbox 一样,可以将多个 ToggleButton 对象组合在一起。默认情况下,每个切换按钮可用于表示两种状态(例如 Switch 小部件中的 ON/OFF)。但是,如果多个切换按钮的 group 属性具有相同的值,则该组中只有一个具有 down 状态。当其中一个分组按钮处于 down 状态时,其余按钮将自动设置为 normal。

btn1 = ToggleButton(text='Male', group='sex',)
btn2 = ToggleButton(text='Female', group='sex', state='down')

在这种情况下,这两个按钮属于同一个组,因此一次只能有一个处于 down 状态。

由于它继承了 Button 类,因此我们可以处理切换按钮上的 on_press 事件。此外,"on_state" 事件可以绑定到其 state 属性。

示例 1

以下代码放置了三个未分组的 ToggleButton。因此,每个按钮都可以设置为 normal 或 down 状态。在下面的示例中,这些切换按钮表示用户要选择的兴趣主题。

self.button1 = ToggleButton(text ="Sports", font_size=32)
self.button2 = ToggleButton(text='Music', font_size=32)
self.button3 = ToggleButton(text='Travel', font_size=32)

它们绑定到回调函数以识别每个按钮的状态。

self.button1.bind(on_press=self.btn1pressed)
self.button2.bind(on_press=self.btn2pressed)
self.button3.bind(on_press=self.btn3pressed)

每个回调方法都检查状态是否为 down,并相应地更新标签文本。

def btn1pressed(self, instance):
   if instance.state=='down':
      self.sports='Sports'
   else:
      self.sports=''
   self.l1.text="{} {} {}".format(self.sports, self.music, self.travel)

此练习的完整代码如下所示:

from kivy.app import App
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720, 350)

class toggledemoapp(App):
   def build(self):
      self.sports = self.music = self.travel = ''

      layout = GridLayout(cols=1, padding=10)
      box = BoxLayout(orientation='horizontal')

      lbl = Label(text="My Interests", font_size=40)
      layout.add_widget(lbl)

      self.l1 = Label(
         text='Choose One or More', font_size=32,
         color=[.8, .6, .4, 1]
      )
      layout.add_widget(self.l1)
      
      self.button1 = ToggleButton(text="Sports", font_size=32)
      self.button2 = ToggleButton(text='Music', font_size=32)
      self.button3 = ToggleButton(text='Travel', font_size=32)

      self.button1.bind(on_press=self.btn1pressed)
      self.button2.bind(on_press=self.btn2pressed)
      self.button3.bind(on_press=self.btn3pressed)

      box.add_widget(self.button1)
      box.add_widget(self.button2)
      box.add_widget(self.button3)

      layout.add_widget(box)
      return layout

   def btn1pressed(self, instance):
      if instance.state == 'down':
         self.sports = 'Sports'
      else:
         self.sports = ''
      self.l1.text = "{} {} {}".format(self.sports, self.music, self.travel)

   def btn2pressed(self, instance):
      if instance.state == 'down':
         self.music = 'Music'
      else:
         self.music = ''
      self.l1.text = "{} {} {}".format(self.sports, self.music, self.travel)

   def btn3pressed(self, instance):
      if instance.state == 'down':
         self.travel = 'Travel'
      else:
         self.travel = ''
      self.l1.text = "{} {} {}".format(self.sports, self.music, self.travel)

toggledemoapp().run()

输出

应用程序以所有按钮都处于 normal 状态打开。尝试将其中任何一个置于“down”状态。

Kivy Toggle Button Choose

示例 2

以下示例以与上图所示类似的布局组合标签和切换按钮,只是切换按钮现在已分组。

我们将使用“kv”脚本设计应用程序布局。Mylayout 类用作应用程序的根,并使用 GridLayout 作为其基础。

class Mylayout(GridLayout):
   def callback(self, *args):
      print (args[0].text)
      self.ids.l1.text=args[0].text

class togglegroupapp(App):
   def build(self):
      return Mylayout()
      
togglegroupapp().run()

这是“kv”语言脚本。它将三个切换按钮放在一个水平框中,该水平框又是一个单列网格布局的一部分。所有按钮的 group 属性都具有相同的值“branch”,并且绑定到上面 MyLayout 类的 callback() 方法。callback() 方法将导致 on_press 事件的按钮的标题放在标签上,以显示用户选择了哪个分支。

<Mylayout>:
   size: root.width, root.height
   cols:1
   Label:
      text:"Which is your Engineering branch?"
      font_size:40
   Label:
      id:l1
      text: 'Choose Any One'
      font_size:32
      color:[.8,.6,.4,1]
   BoxLayout:
      orientation:'horizontal'
      ToggleButton:
         text : "Electronics"
         font_size : 32
         group:'branch'
         on_press:root.callback(*args)
      ToggleButton:
         text:'Mechanical'
         font_size:32
         group:'branch'
         on_press:root.callback(*args)
      ToggleButton:
         text:'Comp.Sci'
         font_size:32
         group:'branch'
         on_press:root.callback(*args)

输出

运行程序并使用按钮状态进行实验,如下所示:

Kivy Toggle Button Branch

Kivy - 相机

使用 Kivy 中的 Camera 小部件,可以显示来自摄像机设备的视频流。Kivy 可能需要一些时间来初始化摄像机设备,并在之后更新小部件纹理。

Camera 类定义在 "kivy.uix.camera: 模块中。

from kivy.uix.camera import Camera
cam = Camera(**kwargs)

如果系统找到多个摄像机设备,则需要通过其索引指定要使用的摄像机。

cam = Camera(index=1)

您还可以使用 resolution 参数指定摄像机分辨率:

cam = Camera(index=1, resolution=(640, 480))

kivy.uix.camera.Camera 类是来自 "kivy.core.camera" 模块的核心 Camera 类的具体实现,并执行初始化和帧捕获功能。

Kivy 需要找到合适的摄像机提供程序才能检测硬件。为此,请安装最新版本的 opencv-python 包,该包还安装其依赖包,包括 NumPy。

pip install opencv-python

要在应用程序窗口上开始从摄像机流式传输馈送,请将 Camera 对象的 play 属性设置为 True,并将其设置为 False 以停止馈送。

cam.play = True

要将摄像机流的快照捕获到图像中,请使用 export_to_png() 方法。指定要保存到的文件名。

Camera 类定义了以下属性:

  • **index** - 使用的摄像机的索引,从 0 开始。将其设置为 -1 以允许自动选择。

  • **play** - 布尔值,指示摄像机是否正在播放。您可以通过设置此属性来启动/停止摄像机:

# create the camera, and start later (default)
cam = Camera()

# and later
cam.play = True

# to sop
cam.play = False
  • **resolution** - 调用摄像机时要使用的首选分辨率。如果您使用 [-1, -1],则分辨率将是默认分辨率。要设置所需的分辨率,前提是设备支持:

cam = Camera(resolution=(640, 480))

示例

以下示例代码在垂直 BoxLayout 中添加了一个 Camera 小部件和一个 ToggleButton。绑定到切换按钮的回调函数在按钮处于 down 状态时将摄像机对象的 play 属性设置为 True,否则将停止视频。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.camera import Camera
from kivy.core.window import Window

Window.size = (720,350)

class TestCameraApp(App):
   def build(self):
      box=BoxLayout(orientation='vertical')
      self.mycam=Camera(play=False, resolution= (640, 480))
      box.add_widget(self.mycam)
      tb=ToggleButton(text='Play', size_hint_y= None, height= '48dp')
      tb.bind(on_press=self.play)
      box.add_widget(tb)
      return box

   def play(self, instance):
      if instance.state=='down':
         self.mycam.play=True
         instance.text='Stop'
      else:
         self.mycam.play=False
         instance.text='Play'
         
TestCameraApp().run()

输出

运行代码并检查输出:

Kivy Camera

您还可以使用“kv”语言脚本设计应用程序窗口布局。将以下脚本另存为“TestCamera.kv”,注释掉 build() 方法中的代码,并在其中放置一个“pass”语句。

BoxLayout:
   orientation: 'vertical'
   Camera:
      id: camera
      resolution: (640, 480)
      play: False
   ToggleButton:
      text: 'Play'
      on_press: camera.play = not camera.play
      size_hint_y: None
      height: '48dp'

Kivy - 树形视图

大多数 GUI 工具包(包括 Kivy)都提供了一个 TreeView 小部件,用户可以使用它以树状格式导航和交互以节点形式呈现的分层数据。操作系统文件浏览器中显示的文件和目录结构是 TreeView 的典型示例。

"kivy.uix.treeview" 模块包含三个重要类的定义:TreeView、TreeViewNode 和 TreeViewLabel。这些类的对象构成树形视图小部件。

TreeView 由 TreeViewNode 实例填充。库中的任何小部件,例如标签或按钮,或用户定义的小部件对象,都与 TreeViewNode 结合使用。

树形视图的根是 TreeView 对象本身。

from kivy.uix.treeview import TreeView
tv = TreeView()

可以在此根节点下方直接添加一个或多个节点。TreeViewLabel 用作 add_node 方法的参数。

from kivy.uix.treeview import TreeViewLabel
n1 = tv.add_node(TreeViewLabel(text='node 1'))

除了将节点添加到树的根节点之外,还可以将其添加到节点本身。将父节点的实例作为第二个参数提供给 add_node() 方法 -

n2 = tv.add_node(TreeViewLabel(text='Political Sci'), n1)

树形视图的根小部件默认情况下处于打开状态,其默认标题为“Root”。要更改它,可以使用 TreeView.root_options 属性。这会将选项传递给根小部件 -

tv = TreeView(root_options=dict(text='My root'))

TreeViewLabel 本身是一个标签,因此无法生成任何事件,例如 on_press。为此,您应该定义一个继承 TreeView 和 Button 的类。

class TreeViewButton(Button, TreeViewNode):
   pass

然后,您可以处理不同的事件,例如 -

  • on_node_expand - 节点展开时触发

  • on_node_collapse - 节点折叠时触发

示例

以下代码构建了一个简单的树形视图,显示了大学每个系提供的科目。垂直盒布局包含一个树形视图小部件和一个按钮。树形视图的根节点处于打开状态。

要展开节点,请单击其左侧的“>”按钮。它会变成向下箭头。如果再次单击,则节点将折叠。

from kivy.app import App
from kivy.uix.treeview import TreeView, TreeViewLabel
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window

Window.size = (720, 350)

class DemoApp(App):
   def build(self):
      lo = BoxLayout(orientation='vertical')
   
      self.tv = TreeView(root_options={
         'text': 'Faculty-wise Subjects',
         'font_size': 20}
      )
      self.n1 = self.tv.add_node(TreeViewLabel(text='Arts'))
      self.n2 = self.tv.add_node(TreeViewLabel(text='Commerce'))
      self.n3 = self.tv.add_node(TreeViewLabel(text='Science'))
      
      self.n4 = self.tv.add_node(
         TreeViewLabel(text='Sociology'), self.n1
      )
      self.n5 = self.tv.add_node(
         TreeViewLabel(text='History'), self.n1
      )
      self.n6 = self.tv.add_node(
         TreeViewLabel(text='Political Sci'), self.n1
      )
      
      self.n7 = self.tv.add_node(
         TreeViewLabel(text='Accountancy'), self.n2
      )
      self.n8 = self.tv.add_node(
         TreeViewLabel(text='Secretarial Practice'), self.n2
      )
      self.n9 = self.tv.add_node(
         TreeViewLabel(text='Economics'), self.n2
      )
      
      self.n10 = self.tv.add_node(
         TreeViewLabel(text='Physics'), self.n3
      )
      self.n11 = self.tv.add_node(
      TreeViewLabel(text='Mathematics'), self.n3
      )
      self.n12 = self.tv.add_node(
         TreeViewLabel(text='Chemistry'), self.n3
      )
      
      lo.add_widget(self.tv)
      return lo
DemoApp().run()

输出

Kivy Tree View

Kivy - reStructuredText

reStructuredText 是一种文本文件格式,其中包含主要用于 Python 技术文档的数据。该文件通常具有“.rst”扩展名。

  • reStructuredText 是 DocUtils 项目的一部分,其主要目的是为 Python 提供一组类似于 Java 的 Javadoc 的工具。由 David Goodger 编写,其最早版本于 2001 年发布,最新版本于 2019 年发布。

  • reStructuredText 可以被认为是一种轻量级标记语言,与 MarkDown 语法有很多相似之处。它被用作 Python 的 Sphinx 文档生成系统的核心组件。

Kivy 以 RstDocument 类的形式提供了 reStructuredText 文档渲染器,该类定义在“kivy.uix.rst”模块中。

from kivy.uix.rst import RstDocument
doc = RstDocument(**kwargs)

格式化 reStructuredText

  • 段落 - 由空行(一个就足够了)分隔的文本块。段落必须具有相同的缩进。

  • 粗体 - 两个星号之间的字符(例如:**Hello**)

  • 斜体 - 单个星号之间的字符(例如:*world*)

  • 枚举列表 - 以数字或字母开头,后跟句点“.”、右括号“)”或括在括号“( )”中。例如 -

1. Python
2. Java
3. C++
  • 项目符号列表 - 以项目符号字符开头 - “-”、 “+” 或 “*”

  • - 这些是带有装饰的单行文本(一个或多个单词):单独的下划线,或下划线和上划线一起,用破折号“-----”、等号“======"

  • 图像 - 要在文档中包含图像,可以使用 image 指令。例如 -

.. image:: kivi-logo.png

示例

以下文本根据 reStructuredText 语法进行格式化。将以下文本保存为 index.rst -

================
Welcome to Kivy
================
Welcome to Kivy's documentation. **Kivy** is an open source software library for the rapid development of applications equipped with novel user interfaces, such as multi-touch apps.
With Kivy, you can create apps that run on:

* Desktop computers: macOS, Linux, *BSD Unix, Windows.
* iOS devices: iPad, iPhone.
* Android devices: tablets, phones.


-------------------
Virtual environment
-------------------
Create the virtual environment named kivy_venv in your current directory_::

   python -m virtualenv kivy_venv
   
Activate the *virtual environment*.

For Windows default CMD, in the command line do_::
   kivy_venv\Scripts\activate
   
Your terminal should now preface the path with something like (kivy_venv), indicating that the kivy_venv environment is active.
If it doesn't say that, the virtual environment is not active and the following won't work.

Install Kivy
------------
The simplest is to install the current stable version of kivy is to use pip_::

   python -m pip install "kivy[base]" kivy_examples

让我们编写一个程序,在 Kivy 应用程序中渲染此 reStructuredText 文档。我们将一个 RstDocument 小部件放在具有单列的网格布局中。将此对象的 source 属性设置为我们创建的 RST 文件。

from kivy.app import App
from kivy.uix.rst import RstDocument
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720,400)

class rstdemoapp(App):
   def build(self):
      grid=GridLayout(cols=1)
      doc = RstDocument(source="index.rst")
      grid.add_widget(doc)
      return grid
rstdemoapp().run()

输出

运行以上代码。RST 文档将根据上面解释的格式化语法显示。

Kivy Restructured Text

Kivy - 操作栏

Kivy 框架提供 ActionBar 小部件,它充当易于访问的菜单,通常位于应用程序窗口的顶部或底部,有点类似于 Android 中的 ActionBar。

ActionBar 类定义在 kivy.uix.actionbar 模块中。操作栏的外观取决于其内部 ActionView 的组成。ActionView 包含一个或多个 ActionButtons,由相应的文本标题和/或图标表示。

Kivy Action Bar

ActionView 包含 ActionButtons、分隔符或 ActionGroup。ActionGroup 是 ActionButtons 的集合。当您单击 ActionGroup 标题时,按钮将显示在下拉菜单中。

当 ActionBar 区域变得太小而无法容纳所有内容时,小部件将移动到 ActionOverflow 区域。

您可能希望始终在 ActionBar 上显示某些 ActionItems,而不管存在多少项。如果将 ActionButton 的 Important 属性设置为 True,它将在栏上获得优先级放置。

示例

以下代码将标签和 ActionBar 放置在应用程序窗口的底部。ActionBar 显示几个 ActionButtons,每个按钮的“on_press”事件都将标签标题更新为其自己的 text 属性。

App 代码由 myActionApp 类组成,其 build 方法通过加载关联的 kv 文件脚本构建窗口的外观。

Python 代码如下所示 -

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.core.window import Window

Window.size = (720,400)

class mywidget(Widget):
   def pressed(self, obj):
   self.ids.l1.text=obj.text
   
class myActionApp(App):
   def build(self):
      return mywidget()
      
myActionApp().run()

这里,mywidget 类是 Widget 类的子类。以下 kv 语言脚本(保存为 myAaction.kv)组合了 ActionBar 和两个按钮。它还放置了一个标签以显示按下 ActionButton 的标题。

<mywidget>
   Label:
      id:l1
      text:'Hello'
      pos_hint:{'center_x':.5, 'center_y':1}
      pos:(root.width/2-150, root.height/2-50)
      font_size:48
      size:(300,100)
   ActionBar:
      size:root.width, 50
      pos_hint: {'top':1}
      background_color: .6, 4, .2, .6
      ActionView:
         use_separator: True
         ActionPrevious:
            title: 'Action Bar'
            with_previous: False
         ActionOverflow:
         ActionButton:
            icon: 'atlas://data/images/defaulttheme/audio-volume-high'
         ActionButton:
            important: True
            text: 'Important'
            on_press:root.pressed(self)
         ActionButton:
            text: 'Btn1'
            on_press:root.pressed(self)

输出

Kivy Action Bar Important

Kivy - 播放器

Kivy 库中的 VideoPlayer 小部件是一个现成的控件,用于播放视频文件并控制其播放和声音。它是一个预定义的布局,包含播放区域和播放/停止以及暂停/恢复播放的按钮。它还有一个可以将播放位置移动到所需位置的 seekbar。

VideoPlayer 类定义在“kivy.uix.videoplayer”模块中。

from kivy.uix.videoplayer import VideoPlayer
player = VideoPlayer(**kwargs)

您需要将 VideoPlayer 对象的 source 属性分配给表示要播放的视频文件的字符串。Kivy 支持不同的视频格式,包括“mp4”、“avi”和“mpg”。

要启动视频,您应该将 state 属性设置为“play”。

from kivy.uix.videoplayer import VideoPlayer
player = VideoPlayer(source = "test.mp4")
player.state = 'play'

Kivy 的 VideoPlayer 的重要功能之一是能够在视频中的特定位置显示文本注释,并持续一段时间。注释存储在扩展名为“.jsa”的文件中,并且与视频文件同名。

.jsa 文件是一种基于 JSON 的文件格式。它包含注释的开始位置、在屏幕上显示的持续时间(以秒为单位)以及注释文本的值。

[
   {"start": 0, "duration": 2,
   "text": "Introduction"},
   {"start": 10, "duration": 5,
   "text": "Hello World"},
]

要使视频循环播放,请将 eos 选项设置为 loop -

options={'eos': 'loop'}

VideoPlayer 类定义了以下属性 -

  • source - 要读取的视频源。source 是一个 StringProperty,表示要播放的视频文件的路径。

  • state - 字符串,指示是播放、暂停还是停止视频。state 是一个 OptionProperty,默认为“stop”。

  • allow_fullscreen - 默认情况下,您可以双击视频使其全屏显示。将此属性设置为 False 以防止此行为。

  • position - 视频在 0 到 duration 之间的位置。position 默认为 -1,并在视频加载时设置为实际位置。

  • seek(percent, precise=True) - 将位置更改为 duration 的百分比(严格来说,是比例)。percent 值应为浮点数或整数,介于 0-1 之间。precise 参数是一个布尔值,默认为 True,其中精确查找速度较慢,但查找至确切的请求百分比。

  • thumbnail - 要显示的视频缩略图。如果为 None,VideoPlayer 将尝试从 source + '.png' 中查找缩略图。

  • volume - 视频的音量范围为 0-1。1 表示最大音量,0 表示静音。

  • annotation - VideoPlayer 使用 VideoPlayerAnnotation 对象根据存储在 JSA 文件中的 JSON 数据构建注释。

允许在 JSA 文件中使用以下键 -

  • start - 显示注释的位置

  • duration - 注释标签在播放器窗口上显示的时间。

  • text - 要显示为注释的文本。

  • bgcolor - [r, g, b, a] - 文本框的背景颜色

  • bgsource - 'filename' - 用于文本框背景的背景图像

  • border - (n, e, s, w) - 用于背景图像的边框

示例

以下代码在应用程序窗口上呈现一个视频播放器小部件 -

from kivy.app import App
from kivy.uix.videoplayer import VideoPlayer
from kivy.core.window import Window

Window.size = (720, 350)

class MainApp(App):
   title = "Simple Video"
   def build(self):
      player = VideoPlayer(
         source="video.mp4",
         size_hint=(0.8, 0.8),
         options={'fit_mode': 'contain'}
      )
      player.state = 'play'
      player.options = {'eos': 'loop'}
      player.allow_stretch = True
      return player
      
MainApp().run()

输出

运行代码并检查输出:

Kivy Video Player

Kivy - 模板视图

Kivy 库中的 StencilView 小部件限制了添加到其中的其他子小部件的画布区域。任何尝试在 StencilView 区域外部绘制的指令都将被剪裁。

StencilView 小部件在后台使用 Stencil 图形指令。它提供了一种有效的方法来剪裁子元素的绘图区域。

StencilView 类定义在“kivy.uix.stencilview”模块中。

from kivy.uix.stencilview import StencilView

需要注意的是,StencilView 不是布局。因此,要将小部件添加到 StencilView,必须将 StencilView 和 Layout 组合起来才能实现布局的行为。此外,不能向 StencilView 添加超过 128 个支持 stencil 的小部件。

StencilView 的一般用法如下 -

st = StencilView(size=(x,y))
w = Widget()
st.add_widget(w)

要了解 StencilView 如何准确地限制小部件的可绘制区域,让我们首先执行某些图形绘制指令,然后应用 StencilView 以查看差异。

示例

在以下代码中,mywidget 类扩展了 Widget 类,并在随机位置和随机 RGB 值下绘制 200 个圆形。(这还没有使用 StencilView)

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import *
import random
from kivy.core.window import Window

Window.size = (720, 350)
class mywidget(Widget):
   def __init__(self, *args):
      super().__init__(*args)
      for i in range(200):
         colorR = random.randint(0, 255)
         colorG = random.randint(0, 255)
         colorB = random.randint(0, 255)
         posx = random.randint(0, Window.width)
         posy = random.randint(0, Window.height)
         self.canvas.add(Color(rgb=(colorR / 255.0, colorG / 255.0, colorB / 255.0)))
         d = 30
         self.canvas.add(Ellipse(pos=(posx, posy), size=(d, d)))

class circlesapp(App):
   def build(self):
      w = mywidget()
      return w

circlesapp().run()

输出

如您所见,圆形在应用程序窗口区域的各个位置随机绘制。

Kivy Stencial View

现在我们将应用 StencilView,并将绘图区域限制在主窗口中心的 400×300 像素。

创建 StencilView 对象,并将 Widget 对象添加到其中。绘图循环保持不变。

class circlesapp(App):
   def build(self):
      st = StencilView(
         size_hint=(None, None), size=(400, 300),
         pos_hint={'center_x':.5, 'center_y':.5}
      )
      w=widget()
      st.add_widget(w)
      return st

如果我们现在运行应用程序,我们应该看到随机圆形出现在 stencil 区域内,因为对于 stencil 区域之外的所有位置,绘图指令都受到限制。

Kivy Stencil Area

Kivy - 虚拟键盘

Kivy 库中的 VKeyboard 小部件对于在智能手机和平板电脑等多点触控设备上运行的应用程序特别有用。VKeyboard 是一个屏幕键盘。其操作旨在对用户透明。

VKeyboard 以两种模式使用 - 停靠自由模式。自由模式适用于多点触控设备,而停靠模式在使用类似平板电脑的计算机时启用。

VKeyboard 类定义在 kivy.uix.vkeyboard 模块中。

from kivy.uix.vkeyboard import VKeyboard
kb = VKeyboard(**kwargs)

虚拟键盘永远不会直接使用。相反,它受配置控制。如果应用程序有任何需要键盘的小部件(例如 TextInput),请不要直接使用虚拟键盘,而是首选在平台上可用的最佳方法。

VKeyboard 类继承自 ScatterLayout。虚拟键盘小部件右下角的一个按钮允许您在可用的布局之间切换。

VKeyboard 对象具有以下属性:

  • available_layouts - 所有可用布局的字典。键是布局 ID,值是 JSON,默认为 {}。

  • callback - 回调可以设置为一个函数,如果用户关闭 VKeyboard,则会调用该函数。

  • docked - 指示 VKeyboard 是否停靠在屏幕上。如果更改它,则必须手动调用 setup_mode(),否则它将不起作用。

  • key_margin - 键边距,用于在键之间创建空间。边距由四个值组成,单位为像素:

key_margin = [top, right, bottom, left]

key_margin 默认为 [2, 2, 2, 2]

  • target - 与 VKeyboard 关联的目标小部件。如果设置,它将用于发送键盘事件。

示例

在以下示例中,一个单列网格布局的初始构成显示了一个标签和一个 TextInput 小部件。当用户通过点击文本框内部生成 touch_down 事件时,一个 VKeyboard 小部件将添加到布局中。

def ontouch(self, instance, value):
   self.kb = VKeyboard(
      on_key_up = self.vkbinput,
      pos_hint={'center_x':.5},
      size_hint=(.8, None)
   )
   self.layout.add_widget(self.kb)

当按下键盘上的任何键时,它会生成 on_key_up 事件。它绑定到 vkbinput() 方法。此方法读取被按下的键的键码并更新文本框的内容。

def vkbinput(self, keyboard, keycode, *args):
   text = self.text1.text

   if keycode == '~':
      self.layout.remove_widget(self.kb)
      return
   
   self.text1.text = f'{text}{keycode}'

每当用户按下“~”键时,键盘小部件将从布局中移除。

完整代码如下所示:

from kivy.app import App
from kivy.uix.vkeyboard import VKeyboard
from kivy.uix.textinput import TextInput
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720, 400)

class MainApp(App):
   def build(self):
      self.layout = GridLayout(cols=1)
      self.text1 = TextInput(
         font_size=32,
         on_touch_down=self.ontouch
      )
      self.label = Label(
         text="Enter Text....",
         font_size=32,
         color=[.8, .6, .1]
      )
      self.layout.add_widget(self.label)
      self.layout.add_widget(self.text1)
      return self.layout
      
   def ontouch(self, instance, value):
      self.kb = VKeyboard(
         on_key_up=self.vkbinput,
         pos_hint={'center_x': .5},
         size_hint=(.8, None)
      )
      self.layout.add_widget(self.kb)

   def vkbinput(self, keyboard, keycode, *args):
      text = self.text1.text
      if keycode == '~':
         self.layout.remove_widget(self.kb)
         return
      self.text1.text = f'{text}{keycode}'

MainApp().run()

输出

当以上代码运行时,应用程序窗口的初始显示如下:

Kivy Vkeyboard

点击文本框内部,虚拟键盘将出现在其下方:

Kivy Vkeyboard Enter Text

您现在可以输入文本。按“~”移除键盘。

Kivy Vkeyboard Remove Text

Kivy - 触摸涟漪

在 Kivy 框架中,“Touch Ripple”(触摸波纹)并不是真正的小部件或任何具体的类。相反,TouchRippleBehavior 混合类为布局或单个小部件添加了触摸波纹视觉效果。通常,Kivy 有一个默认的按下/释放可视化效果。此类添加了来自 Google Material Design 的波纹效果。

此混合类在“kivy.uix.behaviors.touchripple”模块中定义。

from kivy.uix.behaviors.touchripple import TouchRippleBehavior

Ripple 行为不会自动触发。具体的类需要实现此行为混合类并显式地手动调用 ripple_show() 或 ripple_fade() 方法。

要自定义波纹效果,请使用以下属性:

  • ripple_duration_in - 显示叠加层所需的动画持续时间。它是一个 NumericProperty,默认为 0.5。

  • ripple_duration_out - 一个 NumericProperty,默认为 0.2,设置淡出叠加层所需的动画持续时间。

  • ripple_fade_from_alpha - 波纹颜色动画开始时的 Alpha 通道。默认值为 0.5。

  • ripple_fade_to_alpha - 波纹颜色动画目标的 Alpha 通道,默认为 0.8。

  • ripple_rad_default - 动画开始时的默认半径。它是一个 NumericProperty,默认为 10。

  • ripple_scale - 从装饰小部件的 max(width/height) 计算出的动画叠加层的最大缩放比例。

  • ripple_show() 方法开始当前小部件上的波纹动画。您需要将触摸事件作为参数传递。

  • ripple_fade() 方法用于在当前小部件上完成波纹动画。

  • ripple_func_inripple_funcion_out 是显示和隐藏叠加层的动画回调。

示例

在以下示例中,我们使用了 kv 脚本,该脚本将标签放在网格布局中,并处理 touch_down 和 touch_up 事件。

on_touch_down() 方法调用 ripple_show() 方法以生成持续时间为 3 秒的波纹效果。

on_touch_up() 方法通过调用 ripple_fade() 方法来结束波纹效果。

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.behaviors.touchripple import TouchRippleBehavior
from kivy.core.window import Window

Window.size = (720,300)

class RippleLabel(TouchRippleBehavior, GridLayout):
   def __init__(self, **kwargs):
      super(RippleLabel, self).__init__(**kwargs)

   def on_touch_down(self, touch):
      collide_point = self.collide_point(touch.x, touch.y)
      if collide_point:
         touch.grab(self)
         self.ripple_duration_in=3
         self.ripple_show(touch)
         return True
      return False

   def on_touch_up(self, touch):
      if touch.grab_current is self:
         touch.ungrab(self)
         self.ripple_duration_out=3
         self.ripple_fade()
         return True
      return False

class MyRippleApp(App):
   def build(self):
      return RippleLabel(cols=1)

MyRippleApp().run()

“kv”脚本:

<RippleLabel>:
   GridLayout:
      cols:1
      Label:
         size:(root.width, root.height)
         pos_hint:{'center_x':.5, 'center_y':.5}
         
         text:'OK'
         font_size:100
         color:(1,0,0,1)

输出

运行程序并点击“OK”标签。它将在窗口表面上产生波纹。增加持续时间以查看效果。

Kivy Touch Ripple

Kivy – 音频

Kivy 框架提供 Sound 类来处理加载音频文件、播放和停止播放等功能。Sound 类是核心类之一,在“kivy.core.audio”模块中定义。

不建议直接实例化 Sound 对象。相反,请使用如下所示的 SoundLoader 函数:

from kivy.core.audio import SoundLoader
sound = SoundLoader.load('test.mp3')

音频播放由 Gstreamer 实现处理:使用 Gi/Gst 和 PyGST。Gi/GST 适用于 Python 2+3 和 Gstreamer 1.0,而 PyGST 仅适用于 Python 2 + Gstreamer 0.10。

请注意,核心音频库不支持录制音频。如果您需要此功能,请参考 audiostream 扩展。

Sound 对象具有以下重要属性/方法:

序号 属性/方法 & 说明
1 load()

将文件加载到内存中。

2 play()

播放文件。

3 stop()

停止播放。

4 unload()

从内存中卸载文件。

5 seek(position)

跳转到 <position>(以秒为单位)。

6 get_pos()

返回音频文件的当前位置。如果未播放,则返回 0。

7 length

获取声音的长度(以秒为单位)。

8 loop

如果声音在完成后应自动循环,则设置为 True。

9 source

音频文件的名称/来源。

10 state

声音的状态,'stop' 或 'play' 之一。

让我们使用 Sound 对象并在 Kivy 中构建一个简单的音频播放器。应用程序窗口包含一个标签,显示播放器的当前状态,即它是在播放还是已停止,以及两个用于控制播放的按钮。左边的按钮最初标题为“播放”。

Kivy Audio

单击时,它从 mp3 文件加载声音对象,调用 play() 方法,将标签标题更改为“正在播放”并启用暂停按钮,其标题更改为“停止”。

当左按钮的标题为“停止”时被单击时,播放将停止,恢复标签标题并禁用暂停按钮。

当您点击暂停按钮时,音频文件的当前位置将存储在 pos 变量中,按钮标题更改为恢复。当它被点击时,播放位置将通过调用 seek() 方法检索。

示例

以下是完整代码:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.core.audio import SoundLoader
from kivy.core.window import Window

Window.size = (720, 350)

class audiodemoapp(App):
   def build(self):
      layout = GridLayout(cols=1, padding=10)

      self.l1 = Label(
         text='Press Start to Play',
         font_size=40, color=[.8, .6, .4, 1]
      )
      layout.add_widget(self.l1)
      box = BoxLayout(orientation='horizontal')
      self.button1 = Button(text="Play", font_size=32)
      self.button2 = Button(
         text='Pause', font_size=32, disabled=True
      )
      box.add_widget(self.button1)
      box.add_widget(self.button2)
      layout.add_widget(box)
      self.button1.bind(on_press=self.start_stop)
      self.button2.bind(on_press=self.pause_resume)

      return layout

   def start_stop(self, event):
      if self.button1.text == 'Play':
         self.l1.text = 'Playing'
         self.button1.text = 'Stop'
         self.sound = SoundLoader.load('sample.mp3')
         self.pos = 0
         self.button2.disabled = False
         self.sound.play()
      else:
         if self.button1.text == 'Stop':
            self.l1.text = 'Press to Play'
            self.button1.text = 'Play'
            self.sound.unload()
            self.button2.disabled = True
            self.pos = 0

   def pause_resume(self, event):
      if self.button2.text == 'Pause':
         self.button2.text = 'Resume'
         self.l1.text == 'Paused'
         self.pos = self.sound.get_pos()
         print(self.pos)
         self.sound.stop()
      else:
         if self.button2.text == 'Resume':
            self.l1.text = 'Playing'
            self.button2.text = 'Pause'
            print(self.pos)
            self.sound.seek(self.pos)
            self.sound.play()
            
audiodemoapp().run()

输出

下图显示状态标题设置为“正在播放”并且暂停按钮已启用。暂停按钮在暂停和恢复状态之间切换。

Kivy Audio Play

Kivy - 视频

Kivy 框架中的 Video 小部件能够显示视频文件和流。您可以使用 Video 小部件播放的视频格式取决于操作系统、安装的视频提供程序以及任何所需的插件。GStreamer 提供程序能够处理几乎所有视频编解码器,例如 mpg、avi、mp4、mov 等。

Video 类在“kivy.uix.video”模块中定义。

from kivy.uix.video import Video

vid = Video(**args)

构造函数唯一必需的参数是 source 属性 - 一个表示视频文件路径的字符串。

vid = Video(source = "test.mp4")

为了开始视频播放,您需要将其 play 属性设置为 True。您可以在构造函数中传递此参数以在视频加载后立即启动它,或者根据需要将其设置为 True/False。

# start playing the video at creation
video = Video(source='test.mp4', play=True)

# create the video, and start later
video = Video(source='test.mp4')

# and later
video.play = True

Video 类的其他属性如下所示:

  • duration - 视频的持续时间。持续时间默认为“-1”,并在视频加载时设置为实际持续时间。

  • eos - 代表“流结束”。一个布尔属性指示视频是否已完成播放(到达流的末尾)。

  • play - 指示视频是否正在播放。您可以通过将此属性设置为 True 或 False 来启动/停止视频。

  • position - 视频在 0 到 duration 之间的位置。位置默认为 -1,并在视频加载时设置为实际位置。

  • seek() - 将位置更改为作为总持续时间的一部分进行查找,必须在 0-1 之间。

  • state - 一个字符串,指示是播放、暂停还是停止视频:

# start playing the video at creation
video = Video(source='test.mp4', state='play')

# create the video, and start later
video = Video(source='test.mp4')

# and later
video.state = 'play'
  • volume - 视频的音量,范围为 0-1。1 表示全音量,0 表示静音。

示例

from kivy.app import App
from kivy.uix.videoplayer import VideoPlayer
from kivy.uix.video import Video
from kivy.core.window import Window

Window.size = (720,400)

class MainApp(App):
   title = "Simple Video"
   def build(self):
      player = Video(source = "earth.mp4",
         size_hint = (1,1),
         options={'fit_mode': 'contain'})
      player.state = 'play'
      player.options = {'eos': 'loop'}
      player.allow_stretch=True
      return player

MainApp().run()

输出

当您运行以上代码时,视频播放将开始:

Kivy Videos

Kivy - 拼写检查

Kivy 库在其“kivy.core”包中带有一个拼写模块。它提供了对一系列拼写检查后端以及单词建议的抽象访问。API 借鉴了“python-enchant”库。

您需要安装 enchant 才能使用此功能。

pip3 install pyenchant

示例

创建 Spelling 类(在“kivy.core.spelling”模块中定义)的对象以调用其各种方法。例如,list_languages() 方法返回支持的语言列表。

from kivy.core.spelling import Spelling
s = Spelling()
s.list_languages()

输出

它将列出所有支持的语言:

['en_BW', 'en_AU', 'en_BZ', 'en_GB', 'en_JM', 'en_DK',
'en_HK', 'en_GH', 'en_US', 'en_ZA', 'en_ZW', 'en_SG', 'en_NZ',
'en_BS', 'en_AG', 'en_PH', 'en_IE', 'en_NA', 'en_TT', 'en_IN',
'en_NG', 'en_CA']

您可以从列表中选择特定语言以供后续使用。

s.select_language('en_US')

Spelling 类中的 check() 方法检查给定单词在当前活动语言中是否有效。如果是,则返回 True。如果单词不应该被检查,则返回 None(例如对于 '')。如果它不是 self._language 中的有效单词,则返回 False。

>>> s.check('this')
   True
>>> s.check('thes')
   False

您可以从 Spelling 类中获取给定单词的建议。

s.suggest('wold')

['wild', 'wolf', 'old', 'wolds', 'woald', 'world', 'would',
'weld', 'sold', 'woad', 'word', 'told', 'wood', 'cold', 'gold']

如果您尝试选择列表中不存在的支持语言,Kivy 将引发以下 NoSuchLangError 异常:

s.select_language('Hindi')
kivy.core.spelling.NoSuchLangError: Enchant Backend: No
language for "Hindi"

当调用使用语言的方法但之前未选择任何语言时,Kivy 将引发“NoLanguageSelectedError”。

Kivy - 效果

Kivy 库提供“kivy.effects”子包来控制在 Kivy 应用程序中使用 ScrollView 小部件时的过度滚动效果。Effect 类可以执行弹回、更改不透明度或防止滚动超出正常边界的操作。

有三个 Effect 类:

  • ScrollEffect - 用于实现效果的基类。它仅计算滚动和过度滚动。此类在 kivy.effects.scroll 模块中定义

  • DampedScrollEffect - 使用过度滚动信息允许用户比预期拖动更多。一旦用户停止拖动,位置将返回到其中一个边界。此类的定义位于 kivy.effects.dampedscroll 模块中。

  • OpacityScrollEffect − 利用滚动视图部件的滚动超出信息来降低其不透明度。当用户停止拖动时,不透明度会恢复为 1。类的定义可以在 kivy.effects.opacityscroll 模块中找到。

这些类使用 KineticEffect 作为基类,用于根据运动计算速度。

要在 ScrollView 的滚动行为上应用这些类的任何一个效果,请将这些类中的一个设置为 ScrollView 部件的 effect_cls 属性的值。

scr = ScrollView(size=Window.size)
scr.eefect_cls=ScrollEffect

示例

下面的 "kv" 语言脚本构建了一个 ScrollView,并在一个 GridLayout 中添加了 100 个按钮。 "effect_cls" 属性被设置为 ScrollEffect 类。

#:import ScrollEffect kivy.effects.scroll.ScrollEffect
#:import Button kivy.uix.button.Button
<RootWidget>
   effect_cls: ScrollEffect
   GridLayout:
   size_hint_y: None
   height: self.minimum_height
   cols: 1
   on_parent:
      for i in range(100):
self.add_widget(Button(text=str(i), size_hint_y=None))

上面的 "kv" 代码使用了名为 RootWidget 的类规则。以下 Python 代码中 App 类的 build() 方法返回一个 RootWidget 类对象。

from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView
from kivy.effects.dampedscroll import DampedScrollEffect
from kivy.core.window import Window
from kivy.app import App
from kivy.core.window import Window

Window.size = (720,350)

class RootWidget(ScrollView):
   pass
   
class scrollableapp(App):
   def build(self):
      return RootWidget()

scrollableapp().run()

输出

从命令行执行上述 Python 程序。您将得到一个包含滚动视图的应用程序窗口,显示按钮的快照。您可以上下滚动,并激活 ScrollEffect。

Kivy Effects

您可以通过在 RootWidget 类中指定属性来自定义 Effect 类,并将其用作 "effect_cls" 属性。

例如,您可以设置用于滚动的 maxmin 边界。当用户滚动超出边界时,overscroll 属性是计算出的值。

Kivy - 输入记录器

Kivy 框架中 Recorder 类的功能仍在开发中,目前仍处于实验阶段。Recorder 对象记录输入事件,例如触摸事件、键盘事件和点击事件。它们被记录在一个扩展名为 ".kvi" 的文件中。Kivy 使用此文件,通过生成等效的伪事件并将其分派到事件循环来重放事件。

Recorder 类定义在 "kivy.input.recorder" 模块中 -

from kivy.input.recorder import Recorder
rec = Recorder(**kwargs)

要开始录制,在实例化 Recorder 对象后按 F8。事件数据将记录在当前文件夹中的 "recorder.kvi" 文件中。您可以为 file 属性指定任何其他文件名。

rec = Recorder(filename='myrecorder.kvi')

按 F7 重放事件。

要手动控制录制和重放,请使用 Recorder 对象的 record 和 play 属性。

要开始录制 -

rec = Recorder(filename='myrecorder.kvi')
rec.record = True
rec.start()

要停止录制 -

rec.record = False
rec.stop()

类似地,要开始重放 -

rec.play = True
rec.start()

以及,要停止回放 -

rec.play = False
rec.stop()

您可以使回放循环播放 -

def playloop(instance, value):
   if value is False:
      instance.play = True
      
rec = Recorder(filename='myrecorder.kvi')
rec.bind(play=playloop)
rec.play = True

示例

在下面给出的代码中,一个 Label 添加到一个 Scatter 部件上,以便您可以执行旋转、缩放和变换操作。此外,当用户更改 TextInput 框的内容时,标签的 text 属性也会更新。

事件的录制和重放定义在两个按钮的 on_press 事件上。

以下是完整的代码 -

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton
from kivy.input.recorder import Recorder
from kivy.core.window import Window

Window.size = (720,400)

class scatterdemoapp(App):
   def build(self):
      self.rec = Recorder(filename='myrecorder.kvi')

      box=BoxLayout(orientation='vertical')
      box1=BoxLayout(orientation='horizontal')
      text1=TextInput(text='Hi', pos_hint={'top':1},height=100, size_hint=(.5, None))
      b1=ToggleButton(text='record',pos_hint={'top':1}, height=100, size_hint=(.25, None))
      b1.bind(on_press=self.on_recording)
      b2=ToggleButton(text='play', pos_hint={'top':1},height=100, size_hint=(.25, None))
      b2.bind(on_press=self.on_playing)
      box1.add_widget(text1)
      box1.add_widget(b1)
      box1.add_widget(b2)
      box.add_widget(box1)
      scatr=Scatter()
      self.lbl=Label(text="Hi", font_size=60, pos=(Window.width/2-100,200 ))
      
      text1.bind(text=self.lbl.setter('text'))
      scatr.add_widget(self.lbl)
      box.add_widget(scatr)

      return box

   def on_recording(self, obj):
      if obj.state=='down':
         self.rec.record=True
         self.rec.start()
      else:
         self.rec.record=False
         self.rec.stop()
   def on_playing(self, obj):
      if obj.state=='down':
         self.rec.play=True
         self.rec.start()
      else:
         self.rec.play=False
         self.rec.stop()
         
scatterdemoapp().run()

输出

应用程序窗口如下所示 -

Kivy Input Recorder

点击录制按钮,所有屏幕活动(包括 key_down 事件)都将记录在 ".kvi" 文件中。控制台窗口显示输入已记录。

[INFO ] [Recorder ] Recording inputs to 'myrecorder.kvi'
[INFO ] [Recorder ] Recorded 901 events in 'myrecorder.kvi'

按下播放按钮重放已记录的事件。相应地,控制台会回显相应的日志。

[INFO ] [Recorder ] Start playing 901 events from 'myrecorder.kvi'

Kivy - OpenGL

Kivy 框架拥有强大的图形功能,构建在 OpenGL 和 SDL 指令之上。Kivy 使用 OpenGL ES 2 图形库,并基于顶点缓冲对象和着色器。 "kivy.graphics.opengl" 模块是围绕 OpenGL 命令的 Python 包装器。

着色器是一个用户定义的程序,旨在在图形处理器的某个阶段运行。着色器是用 OpenGL 着色语言 (GLSL) 编写的,这是一种高级着色语言,其语法基于 C 编程语言。

在 Web 上创建图形的两种常用着色器是顶点着色器和片段(像素)着色器。

  • 顶点着色器 − 它们接收来自前一管道阶段的输入(例如顶点位置、颜色和光栅化像素),并自定义输出到下一阶段。

  • 片段着色器 − 它们接收所有像素的 2D 位置作为输入,并自定义每个像素的输出颜色。

关于 GLSL 的功能和语法的详细讨论超出了本教程的范围。我们将在本章中使用一个开源着色器文件 (kaleidoscope.glsl)。

#ifdef GL_ES
precision highp float;
#endif

uniform vec2 resolution;
uniform float time;
uniform sampler2D tex0;
uniform sampler2D tex1;

void main(void){
   vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
   vec2 uv;
   float a = atan(p.y,p.x);
   float r = sqrt(dot(p,p));
   
   uv.x = 7.0*a/3.1416;
   uv.y = -time+ sin(7.0*r+time) + .7*cos(time+7.0*a);
   float w = .5+.5*(sin(time+7.0*r)+ .7*cos(time+7.0*a));
   vec3 col = texture2D(tex0,uv*.5).xyz;
   gl_FragColor = vec4(col*w,1.0);
}

在我们的 Kivy 应用程序代码中,我们使用了一个 .kv 文件,该文件只是在 FloatLayout 部件的画布上绘制一个矩形。

<ShaderWidget>:
   canvas:
      Color:
         rgb: 1, 0, 0
   Rectangle:
      pos: self.pos
      size: self.size

此 "kv" 文件的 ShaderWidget 规则对应于 ShaderWidget 类。它安排一个时钟间隔,每秒触发一次,以更新着色器定义中的 glsl 变量。

class ShaderWidget(FloatLayout):
   fs = StringProperty(None)
   def __init__(self, **kwargs):
      self.canvas = RenderContext()
      super(ShaderWidget, self).__init__(**kwargs)
      Clock.schedule_interval(self.update_glsl, 1 / 60.)

"fs" 类变量存储来自 glsl 文件的代码。来自 kivy.graphics 模块的 RenderContext() 方法存储所有必要的绘图信息,即顶点着色器和片段着色器等。

"fs" StringProperty 绑定到 "on_fs()" 方法,该方法将设置着色器。

def on_fs(self, instance, value):
   shader = self.canvas.shader
   old_value = shader.fs
   shader.fs = value
   if not shader.success:
      shader.fs = old_value
      raise Exception('failed')

每次计划的时钟事件发生时,都会调用 update_glsl() 方法。它基本上更新应用程序窗口上每个像素的片段颜色。

def update_glsl(self, *largs):
   self.canvas['time'] = Clock.get_boottime()
   self.canvas['resolution'] = list(map(float, self.size))
   win_rc = Window.render_context
   self.canvas['projection_mat'] = win_rc['projection_mat']
   self.canvas['modelview_mat'] = win_rc['modelview_mat']
   self.canvas['frag_modelview_mat'] = win_rc['frag_modelview_mat']

App 类简单地从 kv 文件和类定义中加载 ShaderWidget。

示例

完整代码如下所示:

from kivy.clock import Clock
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window
from kivy.graphics import RenderContext
from kivy.properties import StringProperty
from kivy.core.window import Window

Window.size=(720,400)
file=open('kaleidoscope.glsl')
shader=file.read()

class ShaderWidget(FloatLayout):
   fs = StringProperty(None)

   def __init__(self, **kwargs):
      self.canvas = RenderContext()
      super(ShaderWidget, self).__init__(**kwargs)
      Clock.schedule_interval(self.update_glsl, 1 / 60.)

   def on_fs(self, instance, value):
      shader = self.canvas.shader
      old_value = shader.fs
      shader.fs = value
      if not shader.success:
         shader.fs = old_value
         raise Exception('failed')

   def update_glsl(self, *largs):
      self.canvas['time'] = Clock.get_boottime()
      self.canvas['resolution'] = list(map(float, self.size)).
      win_rc = Window.render_context
      self.canvas['projection_mat'] = win_rc['projection_mat']
      self.canvas['modelview_mat'] = win_rc['modelview_mat']
      self.canvas['frag_modelview_mat'] = win_rc['frag_modelview_mat']

class 'kaleidoscopeApp(App):
   title='kaleidoscope'
   def build(self):
      return ShaderWidget(fs=shader)

'kaleidoscopeApp().run()

输出

运行此代码并检查输出 -

kivy openGl

Kivy - 文本

Kivy 库中的 "kivy.core.text" 模块充当渲染文本的后端层。但是,只有在 "kivy.uix.label.Label" 部件无法提供令人满意的结果时才应使用它。

此模块有两个重要的用途:一个是派生来自核心 Label 对象的纹理并将其应用于应用程序中的任何部件,另一个是将自定义字体应用于部件的 text 属性,例如标签、按钮或文本输入(或任何具有 text 属性的部件)。

使用纹理

Label 部件(在 "kivy.uix.label" 模块中)在内存中的加载效率通常不高。为了克服这个问题,可以使用核心 label 类的对象,并将其纹理与常规部件一起使用。

首先从 "kivy.core.label" 中导入 Label 类(为避免混淆,将其命名为 CoreLabel)。

from kivy.core.text import Label as CoreLabel
cl=CoreLabel(text="Hi there!", font_size=50, color=(1, 0, 0, 1))

在此对象上调用 refresh() 方法以计算内容并生成纹理。

cl.refresh()

获取纹理和纹理大小。

texture = cl.texture
texture_size = list(texture.size)

现在可以将其与任何部件一起使用。

示例

在以下代码中,我们将一个 label 部件添加到一个垂直框布局中,并使用核心 label 对象的纹理。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
from kivy.core.text import Label as CoreLabel
from kivy.uix.label import Label
from kivy.uix.widget import Widget
from kivy.core.window import Window

Window.size = (720,400)
   class CoreLabelApp(App):
      def build(self):
         cl=CoreLabel(text="Hi there!", font_size=50, color=(1, 0, 0, 1))
         cl.refresh()
         texture = cl.texture
         main=BoxLayout(orientation='vertical')
         l=Label()
         texture_size = list(texture.size)
         l.canvas.add(Rectangle(texture=texture, size=texture_size))
         main.add_widget(l)
         return main
         
CoreLabelApp().run()

输出

它将产生以下输出 -

Kivy Text

自定义字体

所有具有 text 属性的部件都使用默认的一组字体进行渲染,这些字体根据 Kivy 安装的 config.ini 文件中的设置确定。

default_font = ['Roboto', 'data/fonts/Roboto-Regular.ttf',
'data/fonts/Roboto-Italic.ttf', 'data/fonts/Roboto-Bold.ttf',
'data/fonts/Roboto-BoldItalic.ttf']

但是,您可能希望使用特定字体在任何部件上显示文本,它可以是标签、TextInput 框或按钮。为此,您需要下载相关的字体文件(True Type 字体由 .ttf 文件表示)并将其放置在应用程序文件夹中。

为了使这些字体可用于我们的应用程序,必须在应用程序中注册它们。LabelBase 类的 register() 方法用于此目的。LabelBase 是一个抽象类,由您的操作系统使用的特定字体渲染器使用。

register() 是一个静态方法,具有以下参数 -

register(name, fn_regular, fn_italic=None, fn_bold=None,
fn_bolditalic=None)

其中 "name" 是您将在程序中引用字体的字体名称,"fn_regular" 参数是字体的 TTF 文件。

下载 Consolas 字体和 Monotype Corsiva 字体的 TTF 文件,并将它们存储在应用程序文件夹中。

示例

在以下程序中,我们在三个文本输入框中显示相同的字符串。第一个框使用默认字体渲染文本。第二个框使用 Monotype Corsiva,第三个框应用 Consolas 字体。

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.textinput import TextInput
from kivy.core.text import LabelBase
from kivy.core.window import Window

Window.size = (720,400)
class CustomFontApp(App):
   def build(self):
      main=GridLayout(cols=1)
      LabelBase.register(name='font1', fn_regular='consolas.ttf')
      LabelBase.register(name='font2', fn_regular='MonotypeCorsivaFont.ttf')
      txt='Simple Is Better Than Complicated'
      t1=TextInput(text=txt, halign='center', font_size=48)
      t2=TextInput(
         text=txt, halign='center',
         font_name='font2', font_size=48
      )
      t3=TextInput(
         text=txt, halign='center',
         font_name='font1', font_size=48
      )
      main.add_widget(t1)
      main.add_widget(t2)
      main.add_widget(t3)
      return main
   
CustomFontApp().run()

输出

它将生成以下输出窗口 -

Kivy Text Font

Kivy - 文本标记

尽管 Kivy 的 Label 对象具有粗体、斜体和颜色等属性,但它还提供标记功能,可以使用类似于 HTML 标签的语法来装饰标签文本。要使标记生效,需要将标签的 markup 属性设置为 True。

l = Label(text='Hello [b]World[/b]', markup=True)

请注意,kivy 标记只能用于内联样式。与 HTML 中使用尖括号的标签(如 <b>Hello</b>)不同,这里使用方括号(例如:[b]Hello</b])。

使用此标记语法的文本与 HTML 语法非常相似,如下表所示 -

HTML Kivy 标记
<b>bolded text</b> [b]bolded text[/b]
<i>italicized text</i> [i]italicized text[/i]
<u>underlined text</u> [u]underlined text[/u]

以下标签可用于 label 部件的 text 属性的内联样式 -

序号 文本属性和说明
1

[b][/b]

激活粗体文本
2

[i][/i]

激活斜体文本
3

[u][/u]

下划线文本
4

[s][/s]

删除线文本
5

[font=<str>][/font]

更改字体(str 应为 TTF 文件的名称)
6

[font_family=<str>][/font_family]

请求绘制的字体系列。
7

[size=<size>][/size]

更改字体大小。<size> 应为整数。
8

[color=#<color>][/color]

更改文本颜色
9

[anchor=<str>]

在文本中放置一个锚点。
10

[sub][/sub]

相对于其之前的文本,将文本显示在脚注位置。
11

[sup][/sup]

相对于其之前的文本,将文本显示在尾注位置。

如果需要从当前文本中转义标记,请使用 kivy.utils.escape_markup()。

Kivy - 设置

"kivy.uix.settings" 模块包含一个非常有用的功能,允许您处理 Kivy 安装环境的设置参数。您可以在应用程序窗口上打开设置面板,并修改任何配置令牌。

安装 Kivy 软件时,它会创建一个配置文件,其中包含各种参数令牌及其默认值。该文件名为 "config.ini",存储在由 KIVY_HOME 环境变量标识的目录中。

  • Windows 机器上 - 文件存储在 C:\Users\user\.kivy\config.ini 中。

  • Linux 上 - /home/user/.kivy/config.ini。

  • macOS 上 - /Users/user/.kivy/config.ini。

  • Android 上 - /data/data/org.kivy.launcher/files/.kivy/config.ini。

  • iOS 上 - <HOME_DIRECTORY>/Documents/.kivy/config.ini。

要打开设置面板,请调用 App 类的 open_settings() 方法,通常是在 GUI 上的 on_press 事件(或任何其他事件)响应中。

def onpress(self, instance):
   app.open_settings()

我们从一个简单的 Kivy 应用程序开始,该应用程序在窗口上安装了一个 Button。当按钮被按下时,它会调用 onpress() 方法以显示 Kivy 设置面板。

class HelloApp(App):
   def onpress(self, instance):
      app.open_settings()
   def build(self):
      b1=Button(
         text='Click Here', font_size=50,
         on_press=self.onpress
      )
      return b1

app = HelloApp()
app.run()

应用程序运行后,点击按钮进入设置面板。

Kivy Settings

此处显示的设置与您在 config.ini 文件中看到的相同。尝试更改任何配置令牌的值,您将看到对配置文件所做的更改。

有几种可用的设置面板布局。

  • 设置 − 显示设置,左侧带有一个侧边栏,用于在 json 面板之间切换。

  • SettingsWithSidebar − Settings 的一个简单子类。

  • SettingsWithSpinner − 显示设置,顶部带有一个微调器,可用于在 json 面板之间切换。这是默认设置。

  • SettingsWithTabbedPanel − 在 TabbedPanel 中将 json 面板显示为单独的选项卡。

  • SettingsWithNoMenu − 显示单个 json 面板,没有切换到其他面板的方法,也没有关闭按钮。

要使用 SeetingsWithSidebar 布局,请从 kivy.uix.settings 模块导入它,并将其分配为 App 类的 settings_cls 参数的值。

from kivy.uix.settings import SettingsWithSidebar
class HelloApp(App):
   def onpress(self, instance):
      app.open_settings()
   def build(self):
      self.settings_cls = SettingsWithSidebar
      b1=Button(text='Click Here', font_size=50, on_press=self.onpress)
      return b1

现在窗口提供了一个侧边栏,用于在设置面板之间切换。

Kivy Settings Panels

创建新面板

目前,您只有一个名为 Kivy 的面板,它显示 Kivy 配置的默认设置。您可以添加一个新面板来定义应用程序的设置。您需要两样东西 -

  • 一个带有默认值的 ConfigParser 实例。

  • 一个 JSON 对象。

您必须创建和处理 ConfigParser 对象,以告知 kivy 的 configparser 在配置文件中存储哪些设置。SettingsPanel 将从中读取值。这些设置的默认值在您的 JSON 对象中为所有部分/键指定了 setdefaults。

让我们添加 build_config 方法,该方法为按钮文本和字体大小属性提供默认值。

def build_config(self, config):
   config.setdefaults('My Button', {'text': 'Hello Kivy, 'font_size': 20})

build_settings() 方法根据代码中的 JSON 对象在配置中构建一个新面板。

此示例中使用的 JSON 对象为 -

json = '''
[
   {
      "type": "string",
      "title": "Button text",
      "desc": "text on the button",
      "section": "My Button",
      "key": "text"
   },
   {
      "type": "numeric",
      "title": "Button font size",
      "desc": "font size",
      "section": "My Button",
      "key": "font_size"
   }
]

要根据此 JSON 对象的定义添加一个新面板,请定义 build_settings() 方法 -

def build_settings(self, settings):
   settings.add_json_panel('My Button', self.config, data=json)

就是这样。打开设置时,您应该会看到添加了一个新的“我的按钮”面板。

示例

完整代码如下所示:

from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window
from kivy.uix.settings import SettingsWithSidebar

Window.size = (720,350)
json = '''
[
   {
      "type": "string",
      "title": "Button text",
      "desc": "text on the button",
      "section": "My Button",
      "key": "text"
   },
   {
      "type": "numeric",
      "title": "Button font size",
      "desc": "font size",
      "section": "My Button",
      "key": "font_size"
   }
]
'''
class MyApp(App):
   def onpress(self, instance):
      app.open_settings()
      
   def build(self):
      self.settings_cls = SettingsWithSidebar
      self.btn = Button(on_press=self.onpress)
      self.btn.text = self.config.get('My Button', 'text')
      self.btn.font_size = float(self.config.get('My Button', 'font_size'))
      return self.btn
   
   def build_config(self, config):
      config.setdefaults(
         'My Button', {'text': 'Hello Python', 'font_size': 20}
      )
   
   def build_settings(self, settings):
      settings.add_json_panel('My Button', self.config, data=json)
      
   def on_config_change(self, config, section, key, value):
      if section == "My Button":
         if key == "text":
            self.btn.text = value
         elif key == 'font_size':
            self.btn.font_size = float(value)
app=MyApp()
app.run()

输出

打开设置面板时,您现在会看到“我的按钮”面板,其中有两个设置。根据需要修改值。最后,关闭设置对话框并返回查看更改。

Kivy Settings Hello

Kivy - 布局

Kivy 应用程序窗口一次只能容纳一个部件。因此,如果您尝试添加两个按钮,则只会显示第二个按钮。另一方面,一个好的 GUI 需要不同的部件,例如标签、文本输入框、按钮等,以便符合人体工程学地放置。为此,Kivy 框架提供了布局。布局本身是一个能够容纳其他部件的部件。因此,布局被称为容器部件。

在 Kivy 中,提供了不同类型的布局容器。它们都实现了“kivy.uix.layout”模块中定义的 Layout 接口。Layout 接口本身继承了 Widget 类。

此接口的两个最重要的方法是 -

  • add_widget()

  • remove_widget()

add_widget()

此方法用于将新部件作为此布局的子部件添加。其语法如下 -

add_widget(self, widget, *args, **kwargs)

参数

  • 部件 − 要添加到我们的子部件列表中的部件。

  • 索引 − 在列表中插入部件的索引。请注意,0 的默认值表示部件插入列表的开头,因此将在其他同级部件的顶部绘制。

  • 画布 − 要将部件的画布添加到其中的画布。可以是“before”、“after”或 None(用于默认画布)。

remove_widget

此方法用于从此部件的子部件中删除部件。以下是其语法 -

remove_widget(self, widget, *args, **kwargs)

其中,参数“部件”表示要从子部件列表中删除的部件。

请注意,Layout 是一个接口,因此不能直接使用。实现这些方法的 Layout 类是具体的类,如下面的列表所示 -

序号 方法和描述
1

AnchorLayout

部件可以锚定到“顶部”、“底部”、“左侧”、“右侧”或“中心”。

2

BoxLayout

部件按顺序排列,以“垂直”或“水平”方向排列。

3

FloatLayout

部件基本上不受限制。

4

RelativeLayout

子部件相对于布局定位。

5

GridLayout

部件排列在由 rows 和 cols 属性定义的网格中。

6

PageLayout

用于创建简单的多页面布局,以便可以使用边框轻松地在页面之间翻转。

7

ScatterLayout

部件的定位类似于 RelativeLayout,但可以平移、旋转和缩放。

8

StackLayout

部件以 lr-tb(从左到右,然后从上到下)或 tb-lr 顺序堆叠。

在后续章节中,我们将详细讨论这些布局中的每一个,并提供相关的示例。

Kivy - 浮动布局

在 Kivy 中,FloatLayout 使您可以完全控制部件的放置。它不对部件的定位和大小方式施加任何限制。FloatLayout 会遵守其子部件的“pos_hint”和“size_hint”属性。

FloatLayout 类在“kivy.uix.floatlayout”模块中定义。

from kivy.uix.floatlayout import FloatLayout
layout = FloatLayout(**kwargs)

您可以使用 size 参数指定布局大小。它是以像素为单位的宽度和高度的元组。

layout = FloatLayout(size=(300, 300))

当使用 add_widget() 方法将部件放置在 FloatLayout 对象中时,将采用与布局相同的大小。

您可以指定“size_hint”、“pos_hint”、“pos”和“size”属性来定义部件在 FloatLayout 中的大小和位置。

button = Button(
   text='TutorialsPoint',
   size_hint=(.4, .3),
   pos=(100, 100))

这将在 100,100 坐标(从左下角测量)处放置一个按钮,其大小为布局宽度的 40%、布局高度的 30%。

FloatLayout 支持从 Widget 类继承的两个主要方法:add_widget() 和 remove_widget()。

FloatLayout 的基本用法如下所示 -

class FloatApp(App):
   def build(self):
      flo = FloatLayout()
      l1 = Label(text="TutorialsPoint")
      flo.add_widget(l1)
      return flo

您还可以使用“kv”文件填充应用程序窗口,如下所示 -

FloatLayout:
   Label:
      text : 'TutorialsPoint'

示例

让我们设计一个表单,其中包含三个标签、三个文本输入框(其中一个是多行文本框)和一个提交按钮。

为此使用 FloatLayout 对象。标签放置在 x 坐标值为 200 px 的位置,文本框放置在 x 坐标值为 400 px 的位置。标签和文本框的宽度为布局的 10%。单个按钮水平放置在中心。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.graphics import Color, Rectangle
from kivy.core.window import Window

Window.size = (720,350)

class FloatApp(App):
   def build(self):
      flo = FloatLayout()
      l1 = Label(
         text="Name", size_hint=(.2, .1),
         pos=(200, 350), color = [1,1,1,1]
      )
      with l1.canvas:
         Color(0, 1, 0, 0.25)
         Rectangle(pos=l1.pos, size=(350, 50))
      flo.add_widget(l1)
      t1 = TextInput(size_hint=(.4, .1), pos=(400, 350))
      flo.add_widget(t1)
      
      l2 = Label(
         text="Address", size_hint=(.2, .1),
         pos=(200, 250),color = [1,1,1,1]
      )
      with l2.canvas:
         Color(0, 1, 0, 0.25)
         Rectangle(pos=l2.pos, size=(350, 50))
      flo.add_widget(l2)
      t2 = TextInput(
         multiline=True, size_hint=(.4, .1), pos=(400, 250)
      )
      flo.add_widget(t2)
      
      l3 = Label(
         text="College", size_hint=(.2, .1),
         pos=(200, 150), color = [1,1,1,1]
      )
      with l3.canvas:
         Color(0, 1, 0, 0.25)
         Rectangle(pos=l3.pos, size=(350, 50))
      flo.add_widget(l3)
      t3 = TextInput(size_hint=(.4, .1), pos=(400, 150))
      flo.add_widget(t3)

      b1 = Button(
         text='Submit', size_hint = (.2, .1),
         pos_hint = {'center_x':.5, 'center_y':.09}
      )
      flo.add_widget(b1)
      
      return flo
      
FloatApp().run()

输出

运行此代码时,将生成如下所示的输出窗口 -

Kivy Float Layout

请注意,标签通过在标签画布上绘制具有 RGBA 颜色值 (0, 1, 0, 0.25) 的矩形来赋予背景颜色。

如果您希望使用“kv”文件而不是在 build() 方法中设计 UI,则以下是“kv”语言脚本 -

FloatLayout:
   Label:
      id:l1
      text:"Name"
      size_hint:(.2, .1)
      pos:(200, 350)
      color : [1,1,1,1]
      canvas:
         Color :
            rgba: 0, 1, 0, 0.25
         Rectangle:
            pos:self.pos
            size : (350, 50)

   Label:
      id:l2
      text:"Address"
      size_hint:(.2, .1)
      pos:(200, 250)
      color : [1,1,1,1]
      canvas:
         Color :
            rgba: 0, 1, 0, 0.25
         Rectangle:
            pos:self.pos
            size : (350, 50)
      
   Label:
      id:l3
      text:"College"
      size_hint:(.2, .1)
      pos:(200, 150)
      color : [1,1,1,1]
      canvas:
         Color :
            rgba: 0, 1, 0, 0.25
         Rectangle:
            pos:self.pos
            size : (350, 50)
      
   TextInput:
      id:t1
      size_hint:(.4, .1)
      pos:(400, 350)
   TextInput:
      id:t2
      multiline:True
      size_hint:(.4, .1)
      pos:(400, 250)
   TextInput:
      id:t3
      size_hint:(.4, .1)
      pos:(400, 150)
   Button:
      id:b1
      text:'Submit'
      size_hint : (.2, .1)
      pos_hint : {'center_x':.5, 'center_y':.09}

Kivy - 网格布局

GridLayout 是 Kivy 中常用的布局类型之一。GridLayout 对象充当容器网格。它将窗口区域划分为指定数量的行和列,并将其他部件放置在单元格中。

GridLayout 类在 kivy.uix.gridlayout 模块中定义。对象必须至少定义了一个属性 - rows 和 cols。如果您没有指定这两个属性中的任何一个,Kivy 将抛出 GridLayoutException。

from kivy.uix.gridlayout import GridLayout
grid = GridLayout(**kwargs)

GridLayout 对象的属性如下 -

  • cols − 网格中的列数。您不能再将其设置为负值。cols 是 NumericProperty,默认为 None。

  • rows − 网格中的行数。您不能再将其设置为负值。rows 是 NumericProperty,默认为 None。

  • cols_minimum − 每列最小宽度的字典。字典键是列号(例如 0、1、2…)。它是一个 DictProperty,默认为 {}。

  • rows_minimum − 每行最小高度的字典。字典键是行号(例如 0、1、2…)。它是一个 DictProperty,默认为 {}。

  • minimum_width − 自动计算包含所有子部件所需的最小宽度。它是一个 NumericProperty,默认为 0。它是只读的。

  • minimum_height − 自动计算包含所有子部件所需的最小高度。它是一个 NumericProperty,默认为 0。它是只读的。

  • orientation − 布局的方向。此属性决定了部件如何在网格中的连续单元格中放置。“orientation”是 OptionProperty。其有效值为 -

    • 'lr-tb' − 从左到右,从上到下填充单元格。

    • 'tb-lr' − 从上到下,从左到右填充单元格。

    • 'rl-tb' − 从右到左,从上到下填充单元格。

    • 'tb-rl' − 从上到下,从右到左填充单元格。

    • 'lr-bt' − 从左到右,从下到上填充单元格。

    • 'bt-lr' − 从下到上,从左到右填充单元格。

    • 'rl-bt' − 从右到左,从下到上填充单元格。

    • 'bt-rl' − 从下到上,从右到左填充单元格。

orientation 属性的默认值为“lr-tb”。

  • row_default_height − 用于行的默认最小大小。row_default_height 是 NumericProperty,默认为 0。

  • row_force_default − 如果为 True,则忽略子部件的高度和 size_hint_y,并使用默认行高。row_force_default 是 BooleanProperty,默认为 False。

  • spacing −:子部件之间的间距:[spacing_horizontal, spacing_vertical]。spacing 是 VariableListProperty,默认为 [0, 0]。

  • padding − 布局框与其子部件之间的填充:[padding_left, padding_top, padding_right, padding_bottom]。padding 也接受两个参数形式 [padding_horizontal, padding_vertical] 和一个参数形式 [padding]。

默认情况下,所有部件的大小相同。这是因为默认的 size_hint 为 (1,1)。

def build(self):
   self.lo = GridLayout(cols=2)
   self.b1 = Button(text='Button 1', font_size=20)
   self.b2 = Button(text='Button 2', font_size=20)
   self.b3 = Button(text='Button 3', font_size=20)
   self.b4 = Button(text='Button 4', font_size=20)
   self.lo.add_widget(self.b1)
   self.lo.add_widget(self.b2)
   self.lo.add_widget(self.b3)
   self.lo.add_widget(self.b4)
   return self.lo

使用此代码,您将获得以下布局 -

Kivy Grid Layouts

现在,让我们将“Hello”按钮的大小固定为 250px,而不是使用“size_hint_x=None”。

self.lo = GridLayout(cols = 2)

self.b1 = Button(
   text='Button 1', font_size=20,
   size_hint_x=None, width=250
)

self.b2 = Button(text='Button 2', font_size=20)

self.b3 = Button(
   text='Button 3', font_size=20,
   size_hint_x=None, width=250
)

self.b4 = Button(text='Button 4', font_size=20

App 窗口如下所示 -

Kivy Grid Layouts2

如果我们将 row_default_height 定义为 100 并设置“row_force_default=True” -

self.lo = GridLayout(
cols=2, row_force_default=True, row_default_height=100
)
self.b1 = Button(
text='Button 1', font_size=20, size_hint_x=None, width=250
)
self.b2 = Button(text='Button 2', font_size=20)
self.b3 = Button(
text='Button 3', font_size=20, size_hint_x=None, width=250
)
self.b4 = Button(text='Button 4', font_size=20)

现在窗口如下所示 -

Kivy Grid Layouts

示例 1

在此程序中,我们在具有四列的网格布局中添加了 15 个按钮。每个按钮的标题都是一个唯一的随机生成的数字,介于 1 到 15 之间。random 模块中的 randint() 函数用于此目的。

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.config import Config
import random

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class MyApp(App):
   def build(self):
      self.grid = GridLayout(cols = 4)
      nums=[]
      for i in range(1,16):
         while True:
            num = random.randint(1,15)
            if num not in nums:
               nums.append(num)
               self.grid.add_widget(Button(text = str(num)))
               break
      return self.grid

if __name__ == '__main__':
   MyApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Grid Layouts

示例 2

Kivy 中的 GridLayout 没有跨行和/或列扩展部件的规定。也不可能按行号和列号放置部件。

需要在两列网格中排列一些标签和文本输入框,但下面的提交按钮应跨两列。为此,我们在单列网格内放置一个两列网格,并在其下方放置一个按钮。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720, 400)

class DemoApp(App):
   def build(self):
      self.grid = GridLayout(rows=2)
      self.top_grid = GridLayout(
         cols=2, row_force_default=True,
         row_default_height=100
      )
      self.top_grid.cols = 2
      self.top_grid.add_widget(
         Label(text="Name: ", size_hint_x=None, width=250)
      )
      self.fname = TextInput(
         multiline=False, size_hint_x=None, width=650
      )
      self.top_grid.add_widget(self.fname)
      self.top_grid.add_widget(Label(text="Address: "))
      self.address = TextInput(multiline=True)
      self.top_grid.add_widget(self.address)
      self.top_grid.add_widget(Label(text="College: "))
      self.college = TextInput(multiline=False)
      self.top_grid.add_widget(self.college)
      self.grid.add_widget(self.top_grid)
      self.b1 = Button(text='Submit', size_hint_y=None, height=200)
      self.grid.add_widget(self.b1)
      return self.grid
   
if __name__ == '__main__':
   DemoApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Grid Layouts Submit

Kivy - 箱式布局

Kivy 框架提供了 BoxLayout 类,我们可以使用它按顺序排列部件。顺序由 BoxLayout 对象的 orientation 属性确定,可以是字符串:“vertical”或“horizontal”。

BoxLayout 类在“kivy.uix.boxlayout”模块中定义。要声明 BoxLayout 对象,请使用。

from kivy.uix.boxlayout import BoxLayout
blo = BoxLayout(**kwargs)

属性

  • orientation − 布局的方向。orientation 是 OptionProperty,默认为“horizontal”。可以是“vertical”或“horizontal”。

  • padding − 布局框与其子部件之间的填充:[padding_left, padding_top, padding_right, padding_bottom]。padding 也接受两个参数形式 [padding_horizontal, padding_vertical] 和一个参数形式 [padding]。

  • minimum_height − 自动计算包含所有子部件所需的最小高度。minimum_height 是 NumericProperty,默认为 0。它是只读的。

  • minimum_size − 自动计算包含所有子部件所需的最小大小。minimum_size 是 (minimum_width, minimum_height) 属性的 ReferenceListProperty。它是只读的。

  • minimum_width − 自动计算包含所有子部件所需的最小宽度。minimum_width 是 NumericProperty,默认为 0。它是只读的。

Kivy Box Layouts Kivy Vertical Box Layouts

BoxLayout 类还继承了add_widget()remove_widget()方法,我们之前已经讨论过这些方法。

垂直 BoxLayout

BoxLayout 的典型用法如下所示。我们在垂直框布局中添加了一个标签、一个文本输入和一个按钮。

示例

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window
Window.size = (720,200)

class DemoApp(App):
   def build(self):
      lo = BoxLayout(orientation = 'vertical')
      self.l1 = Label(text='Enter your name', font_size=20)
      self.t1 = TextInput(font_size = 30)
      self.b1 = Button(text = 'Submit', size_hint = (None, None),pos_hint={'x':.4, 'y':.2}, size = (200,75))
      lo.add_widget(self.l1)
      lo.add_widget(self.t1)
      lo.add_widget(self.b1)
      return lo

if __name__ == '__main__':
   DemoApp().run()

输出

它将产生以下输出 -

Kivy Box Layouts Name

您可以使用以下“Demo.kv”文件构建上述 GUI -

BoxLayout:
   orientation : 'vertical'
   Label:
      id : l1
      text : 'Enter your name'
      font_size : '20pt'
   TextInput:
      id : t1
      font_size : 30
   Button:
      id : b1
      text : 'Submit'
      size_hint : (None, None)
      pos_hint : {'x':.4, 'y':.2}
      size : (200,75)

水平 BoxLayout

在以下程序中,我们在具有水平方向的框布局中放置了一个标签、一个文本输入框和一个按钮。

示例

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.core.window import Window

Window.size = (720,200)

class DemoApp(App):
   def build(self):
      self.lo = BoxLayout(orientation = 'horizontal')
      self.l1 = Label(text='Enter your name', font_size=20)
      self.t1 = TextInput(font_size = 30, pos_hint={'y':0.25}, pos = (0,100), size_hint = (None, None), size = (650,100))
      self.b1 = Button(text = 'Submit', size_hint = (None, None),pos_hint={'x':.4, 'y':.35}, size = (75, 40))
      self.lo.add_widget(self.l1)
      self.lo.add_widget(self.t1)
      self.lo.add_widget(self.b1)
      return self.lo

if __name__ == '__main__':
   DemoApp().run()

输出

它将产生以下输出 -

Kivy Input Box

您可以使用以下“Demo.kv”文件获得相同的 GUI 设计 -

BoxLayout:
   orientation : 'horizontal'
   Label:
      text : 'Enter your name'
      font_size : '20pt'
   TextInput :
      font_size : '30pt'
      pos_hint : {'y':0.25}
      pos : (0,100)
      size_hint : (None, None)
      size : (650,100)
   Button :
      text : 'Submit'
      size_hint : (None, None)
      pos_hint : {'x':.4, 'y':.35}
      size : (75, 40)

Kivy - 堆叠布局

StackLayout 类的对象充当小部件容器,其中子小部件并排放置,可以水平或垂直放置,具体取决于 orientation 属性。布局尺寸可以容纳尽可能多的部件。每个部件的尺寸可以是不同的。

假设 StackLayout 对象被配置为从左到右、从上到下放置部件。在水平放置“x”个部件后,如果无法在同一行放置部件“x+1”,则将其推到下一行,依此类推,直到布局的高度用尽。

StackLayout 类定义在“kivy.uix.stacklayout”模块中。

from kivy.uix.stacklayout import StackLayout
stack = StackLayout(**kwargs)

通过定义以下属性来自定义 StackLayout 对象:

  • minimum_width − 自动计算包含所有子部件所需的最小宽度。它是一个 NumericProperty,默认为 0。它是只读的。

  • minimum_height − 自动计算包含所有子部件所需的最小高度。它是一个 NumericProperty,默认为 0。它是只读的。

  • minimum_height − 自动计算包含所有子部件所需的最小高度。minimum_height 是 NumericProperty,默认为 0。它是只读的。

  • minimum_size − 自动计算包含所有子部件所需的最小大小。minimum_size 是 (minimum_width, minimum_height) 属性的 ReferenceListProperty。它是只读的。

  • minimum_width − 自动计算包含所有子部件所需的最小宽度。minimum_width 是 NumericProperty,默认为 0。它是只读的。

  • orientation − 布局的方向。此属性决定了部件如何在网格的连续单元格中放置。orientation 是一个 OptionProperty。其有效值为:

    • 'lr-tb' − 从左到右,从上到下填充单元格。

    • 'tb-lr' − 从上到下,从左到右填充单元格。

    • 'rl-tb' − 从右到左,从上到下填充单元格。

    • 'tb-rl' − 从上到下,从右到左填充单元格。

    • 'lr-bt' − 从左到右,从下到上填充单元格。

    • 'bt-lr' − 从下到上,从左到右填充单元格。

    • 'rl-bt' − 从右到左,从下到上填充单元格。

    • 'bt-rl' − 从下到上,从右到左填充单元格。

orientation 属性的默认值为“lr-tb”。

示例

以下程序演示了 StackLayout 的用法。如前所述,默认方向为“lr-tb”。从左到右,然后从上到下按顺序放置宽度逐渐增加但高度相同的按钮。

当下一个按钮无法容纳在当前行时,它会被向下推。每个按钮都带有 1 到 50 之间的随机生成的唯一数字作为标题。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.stacklayout import StackLayout
import random

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class StackApp(App):
   def build(self):
      stack = StackLayout()
      width=100
      nums=[]
      for i in range(1, 25):
         while True:
            num = random.randint(1,25)
            if num not in nums:
               nums.append(num)
               btn = Button(
                  text = str(num), width=width,
                  size_hint=(None, 0.15)
               )
               width = width+num*2
               stack.add_widget(btn)
               break
      return stack
StackApp().run()

输出

它将生成如下所示的堆叠布局:

Kivy Stack Layout

如果尝试调整窗口大小,按钮的位置将相应更改,并且它们要么被容纳在上一行,要么被向下推。

让我们将布局的方向更改为“tb-lr”。

stack = StackLayout(orientation='tb-lr')

再次运行程序并查看更改:

Kivy Change Orientation

再次将 orientation 属性更改为“rl-bt”并运行程序:

stack = StackLayout(orientation='rl-bt')

布局开始从右到左、从下到上填充按钮。因此,生成的窗口如下所示:

Kivy Stack Layout Right To Left

Kivy - 锚点布局

使用此布局时,我们可以以某种方式在其内部排列部件,使其锚定到布局尺寸的某个位置。AnchorLayout 类定义在“kivy.uix.anchorlayout”模块中。

from kivy.uix.anchorlayout import AnchorLayout
lo = AnchorLayout(**kwargs)

关键字参数

  • anchor_x − 定义要放置的小部件的水平锚点。它是一个 OptionProperty,其值必须来自“left”、“center”或“right”。默认为“center”。

  • anchor_y − 定义要放置的小部件的垂直锚点。它是一个 OptionProperty,其值必须来自“top”、“center”或“bottom”。默认为“center”。

  • padding − 小部件框与其子部件之间的填充,以像素为单位:[padding_left, padding_top, padding_right, padding_bottom]。它也接受两个参数的形式 [padding_horizontal, padding_vertical] 和一个参数的形式 [padding]。padding 是一个 VariableListProperty,默认为 [0, 0, 0, 0]。

Kivy Anchor Layout

AnchorLayout 类继承了我们已在前几章中介绍过的两种方法“add_widget()”和“remove_widget()”。

示例 1

以下示例显示了 AnchorLayout 的典型用法:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.anchorlayout import AnchorLayout
from kivy.core.window import Window

Window.size = (720, 400)

class DemoApp(App):
   def build(self):
      lo = AnchorLayout(
         anchor_x='left', anchor_y='bottom'
      )
      self.l1 = Label(
         text='Hello World', font_size=20,
         size_hint=(None, None), size=(200, 75)
      )
      lo.add_widget(self.l1)
      return lo

if __name__ == '__main__':
   DemoApp().run()

输出

可以看出,标签已锚定到布局的左下角。

Kivy Anchor Layout Hello World

使用“kv”语言脚本也可以构建带有 AnchorLayout 的 Kivy 应用程序:

AnchorLayout:
   anchor_x : 'left'
   anchor_y : 'bottom'
   Label:
      id:l1
      text: 'Hello World'
      font_size: '20pt'
      size_hint : (None, None)
      size : (200, 75)

示例 2

在下面的示例中,应用程序窗口具有一个顶级 GridLayout,其中包含要排列成 3 行的小部件。在每一行中,我们放置三个 AnchorLayout,这样窗口包含九个 AnchorLayout,它们具有 lefttop、left-center、left-bottom、center-top、center-center、center-bottom、right-top、right-center 和 right-bottom anchor-x 和 anchor-y 属性。

在每个布局中,根据锚点配置放置一个按钮部件。

from kivy.app import App
from kivy.uix.button import Button
from kivy.config import Config
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.gridlayout import GridLayout

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class AnchorApp(App):
   def build(self):
      lo = GridLayout(rows=3)
      ax = ['left', 'center', 'right']
      ay = ['top', 'center', 'bottom']
      c = 0

      # 3X3 grid with anchor layout in each
      for i in range(3):
         for j in range(3):
            print(ax[i], ay[j])
            anchrlo = AnchorLayout(
               anchor_x=ax[i], anchor_y=ay[j]
         )
         b = Button(
            text=ax[i] + "-" + ay[j],
            size_hint=(None, None),
            size=(200, 75), halign=ax[i]
         )
         
         # red text color for top row,
         # green for middle row,
         # blue for bottom row
         if i == 0: b.color = [1, 0, 0, 1]
         if i == 1: b.color = [0, 1, 0, 1]
         if i == 2: b.color = [0, 0, 1, 1]
         
         anchrlo.add_widget(b)
         lo.add_widget(anchrlo)
      return lo
AnchorApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Anchor Layout 3 Rows

Kivy - 相对布局

RelativeLayout 的行为与 FloatLayout 非常相似。两者之间的主要区别在于,RelativeLayout 中子部件的位置坐标相对于布局大小,而不是像 FloatLayout 那样相对于窗口大小。

要了解这意味着什么,请考虑使用 FloatLayout 设计的以下 UI。

Kivy Relative Layout

当调整窗口大小时,由于 FloatLayout 中的绝对定位,部件的放置与调整大小的窗口不成比例。结果,界面设计不一致。

Kivy Resize Window

RelativeLayout 没有这种影响,因为部件的大小和位置相对于布局。

当带有位置 (0,0) 的部件添加到 RelativeLayout 时,当 RelativeLayout 的位置发生变化时,子部件也会移动。子部件的坐标始终相对于父布局。

RelativeLayout 类定义在“kivy.uix.relativelayout”模块中。

from kivy.uix.relativelayout import RelativeLayout
rlo = RelativeLayout(**kwargs)

示例

以下代码在 RelativeLayout 中组装标签、文本框和提交按钮。

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.relativelayout import RelativeLayout
from kivy.core.window import Window

Window.size = (720, 400)

class RelDemoApp(App):
   def build(self):
      rlo = RelativeLayout()
      
      l1 = Label(
         text="Name", size_hint=(.2, .1),
         pos_hint={'x': .2, 'y': .75}
      )
      rlo.add_widget(l1)
      t1 = TextInput(
         size_hint=(.4, .1), pos_hint={'x': .3, 'y': .65}
      )
      rlo.add_widget(t1)
      l2 = Label(
         text="Address", size_hint=(.2, .1),
         pos_hint={'x': .2, 'y': .55}
      )
      rlo.add_widget(l2)
      t2 = TextInput(
         multiline=True, size_hint=(.4, .1),
         pos_hint={'x': .3, 'y': .45}
      )
      rlo.add_widget(t2)
      l3 = Label(
         text="College", size_hint=(.2, .1),
         pos_hint={'x': .2, 'y': .35}
      )
      rlo.add_widget(l3)
      t3 = TextInput(
         size_hint=(.4, .1), pos=(400, 150),
         pos_hint={'x': .3, 'y': .25}
      )
      rlo.add_widget(t3)
      b1 = Button(
         text='Submit', size_hint=(.2, .1),
         pos_hint={'center_x': .5, 'center_y': .09}
      )
      rlo.add_widget(b1)
      return rlo
      
RelDemoApp().run()

输出

执行上述代码后,应用程序窗口将显示如下所示的 UI:

Kivy Relative Layout UI

请注意,与 FloatLayout 不同,调整窗口大小不会改变部件的比例大小和位置。

Kivy College Form

“kv”设计语言脚本

用于生成上述 UI 而不是 App 类中的 build() 方法的“kv”文件如下:

RelativeLayout:
   Label:
      text:"Name"
      size_hint:(.2, .1)
      pos_hint:{'x':.2, 'y':.75}

   TextInput:
      size_hint:(.4, .1)
      pos_hint:{'x':.3, 'y':.65}
   
   Label:
      text:"Address"
      size_hint:(.2, .1)
      pos_hint:{'x':.2, 'y':.55}
   
   TextInput:
      multiline:True
      size_hint:(.4, .1)
      pos_hint:{'x':.3, 'y':.45}
   
   Label:
      text:"College"
      size_hint:(.2, .1)
      pos_hint:{'x':.2, 'y':.35}
   
   TextInput:
      size_hint:(.4, .1)
      pos:(400, 150)
      pos_hint:{'x':.3, 'y':.25}
   
   Button:
      text:'Submit'
      size_hint : (.2, .1)
      pos_hint : {'center_x':.5, 'center_y':.09}

Kivy - 页面布局

Kivy 中的 PageLayout 类与 Kivy 中的其他容器部件略有不同。使用 PageLayout,您可以创建一个简单的多页布局,以便可以通过边框轻松地在页面之间翻页。

从一个页面到下一个页面的转换是通过从右侧或左侧的边框区域滑动完成的。由于 PageLayout 不支持 size_hint、size_hint_min、size_hint_max 或 pos_hint 属性,因此每个页面只能显示一个小部件。但是,您可以通过将复合布局对象(如 BoxLayout、GridLayout 或 FloatLayout)放在单个页面中,在一个页面中放置多个部件。

PageLayout 类定义在“kivy.uix.pagelayout”模块中。

from kivy.uix.pagelayout import PageLayout
pg = PageLayout(**kwargs)

您可以将以下属性定义为 PageLayout 构造函数的关键字参数:

  • anim_kwargs − 用于构造动画的动画 kwargs。它是一个 DictProperty,默认为 {'d': .5, 't': 'in_quad'}。

  • border − 当前页面周围边框的宽度,用于在需要时显示前一个/下一个页面的滑动区域。它是一个 NumericProperty,默认为 50dp。

  • page − 当前显示的页面,它是一个 NumericProperty,默认为 0。

  • swipe_threshold − 用于触发滑动的阈值,以部件大小的比率表示。swipe_threshold 是一个 NumericProperty,默认为 0.5。

PageLayout 识别触摸事件,您可以覆盖以下事件处理程序:

  • on_touch_down(touch) − 接收一个触摸按下事件,其中 touch 参数为 MotionEvent 类。它返回一个布尔值。如果为 True,则触摸事件的调度将停止。如果为 False,则事件将继续调度到部件树的其余部分。

  • on_touch_move(touch) − 接收触摸移动事件。touch 位于父级坐标中。

  • n_touch_up(touch) − 接收一个触摸抬起事件。触摸位于父坐标中。

这是一个简单的 PageLayout 示例。我们将三个按钮分别作为单独的页面放置。

class PageApp(App):
   def build(self):
      pg = PageLayout()
      btn1 = Button(text ='Page 1')
      btn2 = Button(text ='Page 2')
      btn3 = Button(text ='Page 3')
      pg.add_widget(btn1)
      pg.add_widget(btn2)
      pg.add_widget(btn3)
      return pg

运行时,带有页面 1 标题的按钮显示。按住鼠标并从右向左滑动,以显示第二页和第三页。从左向右滑动使前几页聚焦。

Kivy

示例

在以下示例中,我们在 PageLayout 中添加了两个 FloatLayout。“kv”文件用于设计 UI。

首先,运行 PageLayout 应用程序的 Python 代码:

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.pagelayout import PageLayout
from kivy.config import Config

Config.set('graphics', 'width', '720')
Config.set('graphics', 'height', '400')
Config.set('graphics', 'resizable', '1')

class PageDemoApp(App):
   def build(self):
      pass

if __name__ == '__main__':
   PageDemoApp().run()

以下是“PageDemo.kv”文件脚本。PageLayout 嵌入到 FloatLayout 对象中。上方的 FloatLayout 是注册页面的设计,底部的 FloatLayout 是由登录屏幕组成的第二页。

PageLayout:
   FloatLayout:
      orientation:'vertical'
      Label:
         text:'Register'
         font_size:'20pt'
         pos_hint:{'center_x': .5, 'center_y': .9}
      Label:
         text:"Name"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.75}

      TextInput:
         size_hint:(.4, .1)
         pos_hint:{'x':.3, 'y':.65}

      Label:
         text:"email"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.55}

      TextInput:
         size_hint:(.4, .1)
         pos_hint:{'x':.3, 'y':.45}

      Label:
         text:"Password"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.35}

      TextInput:
         password:True
         size_hint:(.4, .1)
         pos:(400, 150)
         pos_hint:{'x':.3, 'y':.25}

      Button:
         text:'Submit'
         size_hint : (.2, .1)
         pos_hint : {'center_x':.5, 'center_y':.09}

   FloatLayout:
      orientation:'vertical'
      size:(720,400)
      canvas.before:
         Color:
            rgba: 0,0,0, 1
         Rectangle:
            pos: self.pos
            size: self.size

      Label:
         text:"Login"
         font_size: '30pt'
         pos_hint:{'center_x': .5, 'center_y': .75}
      Label:
      text:"email"
      size_hint:(.2, .1)
      pos_hint:{'x':.2, 'y':.55}

      TextInput:
         size_hint:(.4, .1)
         pos_hint:{'x':.3, 'y':.45}
      
      Label:
         text:"Password"
         size_hint:(.2, .1)
         pos_hint:{'x':.2, 'y':.35}

      TextInput:
         password:True
         size_hint:(.4, .1)
         pos:(400, 150)
         pos_hint:{'x':.3, 'y':.25}

      Button:
         text:'Submit'
         size_hint : (.2, .1)
         pos_hint : {'center_x':.5, 'center_y':.09}

输出

保存“PageDemoApp.py”和“PageDemo.kv”这两个文件,然后运行 Python 脚本。您应该首先获得注册页面。

Kivy Page Layout Register

现在,从右向左滑动屏幕以使登录页面出现在应用程序窗口中:

Kivy Page Layout Login

Kivy - 回收布局

很多时候,如果应用程序界面需要大量的小部件来显示数据项,其性能就会下降。Kivy 中的 RecycleView 小部件提供了一种灵活的替代方案。使用 RecycleView,可以仅查看大型数据集的选定部分。它能够重用部件以显示可滚动的列表数据项。此功能对于让用户滚动浏览产品列表、图像等非常有用。

回收布局的机制基于 MVC(模型-视图-控制器)架构。

  • RecycleView.data 属性构成模型层。

  • RecycleDataModel 类实现视图层。视图在布局和视图之间分割,并使用适配器实现。它定义在 RecycleLayout 中,RecycleLayout 是一个抽象类。

  • 对于第三个组件——控制器——RecycleViewBehavior 类定义了逻辑交互。RecycleView 类实现逻辑。它定义在“kivy.uix.recycleview”模块中。

需要实例化 RecycleView 对象,它会自动创建视图和数据类。RecycleView 的两个必须设置的重要属性是 viewclass 和 data。

  • viewclass − 将此属性设置为部件类的名称。可回收视图将由此类的对象组成。例如,如果 viewclass 是 Button,则回收视图将是按钮的可滚动列表。任何部件都可以用作此属性的值。

  • data − data 本质上是一个字典列表,并使用这些字典根据需要生成 viewclass 的实例。这是一个字典列表,其键映射到 viewclass 的对应属性名称。例如,如果 viewclass 设置为 Button,则 data 可以是字典项列表,在每个字典中,键应为 Button 类的属性之一。

RecycleView 部件树还必须包含某个布局管理器,以便可以找到视口。有两个布局可用于 RecycleView。一个是RecycleBoxLayout(在“kivy.uix.recycleboxlayout”模块中可用),另一个是RecycleGridLayout(在“kivy.uix.recyclegridlayout”模块中)。这两个布局类都继承了 BoxLayout 和 GridLayout 类。

示例

让我们在 RecycleView 部件中加载一百个按钮。它们被添加到 RecycleBoxLayout 中。

要将按钮用作回收视图中的元素,请将其 viewclass 设置为 Button。

它的数据属性是一个字典列表。每个字典都有一个“text”键,每个键的值是按钮标题,例如BTN 1、BTN 2等。下面的列表推导式语句构建了字典:

data=[{'text': 'BTN {}'.format(str(x))} for x in range(20)]

[{'text': 'BTN 0'}, {'text': 'BTN 1'}, {'text': 'BTN 2'},
{'text': 'BTN 3'}, {'text': 'BTN 4'}, {'text': 'BTN 5'},
{'text': 'BTN 6'}, {'text': 'BTN 7'}, {'text': 'BTN 8'},
{'text': 'BTN 9'}, {'text': 'BTN 10'}, {'text': 'BTN 11'},
{'text': 'BTN 12'}, {'text': 'BTN 13'}, {'text': 'BTN 14'},
{'text': 'BTN 15'}, {'text': 'BTN 16'}, {'text': 'BTN 17'},
{'text': 'BTN 18'}, {'text': 'BTN 19'}]

以下是RecycleView应用程序的“kv”语言脚本:

RecycleView:
   viewclass: 'Button'
   data: [{'text': 'BTN {}'.format(str(x))} for x in range(20)]
   RecycleBoxLayout:
      default_size: None, dp(56)
      default_size_hint: 1, None
      size_hint_y: None
      height: self.minimum_height
      orientation: 'vertical'

App类只需要加载这个“kv”文件,只需保留build()方法并使用pass语句即可。

from kivy.app import App
from kivy.uix.button import Button
from kivy.core.window import Window

Window.size = (720,400)
class recycled(App):
   def build(self):
      pass

recycled().run()

输出

通过运行上面的Python脚本测试代码的输出。您应该会看到一个如下所示的按钮滚动视图:

Kivy Recycle Layout

Kivy - 布局嵌套

Kivy中的布局是一个容器小部件。它提供了一种有效的机制来构建具有不同小部件的GUI。Kivy库包含不同的布局小部件,例如GridLayout、BoxLayout、AnchorLayout等。

布局本身是Widget类的子类。因此,我们可以策略性地将一个或多个布局放置在布局内。这样,就可以构建一个小部件结构的层次结构,为应用程序设计有效的GUI。任何布局都可以包含在任何其他布局中。这完全取决于开发人员如何设想应用程序的设计。

让我们根据以下示意图规划应用程序界面布局:

Kivy Layouts In Layouts

这里,我们需要一个最上面的单列网格布局。在它里面,我们想放一个TextInput和另外两个水平的盒子布局。

第一个盒子包含三个按钮。它下面的盒子包含一个图像控件和一个AnchorLayout小部件,其中包含一个滑块控件。

示例

以下“kv”语言脚本使用了这种计划好的结构:

GridLayout:
   cols: 1
   size: root.width, root.height
   TextInput:
      text: "Hello World!"
      font_size: 32
      size_hint: (1, .2)
   BoxLayout:
      orientation:'horizontal'
      size_hint: (1, .2)
      Button:
         text:"Btn 1"
         font_size:32
      Button:
         text:"Btn 2"
         font_size:32
      Button:
         text:"Btn 3"
         font_size:32
   FloatLayout:
      Image:
         source: "kivy-logo.png.png"
         size_hint: .5, .75
         pos_hint: {"center_x": 0.25}
      AnchorLayout:
         anchor_x : 'center'
         anchor_y : 'top'
         size_hint: .5, .75
         pos_hint: {"center_x": 0.75}
         Slider:
            min:0
            max:100
            value:25

您可以在以下App类中加载此“kv”脚本:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.animation import Animation
from kivy.core.window import Window

Window.size = (720,400)

class MyLayoutApp(App):
   def build(self):
      pass
      
if __name__ == '__main__':
   MyLayoutApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Layouts In Layouts Btn

应用程序窗口的最终外观类似于初始计划,它使用了“布局中的布局”。

Kivy - 配置对象

安装Kivy软件时,它会创建一个配置文件,其中包含各种参数及其默认值。该文件名为“config.ini”,存储在由KIVY_HOME环境变量标识的目录中。

  • Windows上:该文件存储在C:\Users\user\.kivy\config.ini。

  • Linux上:/home/user/.kivy/config.ini。

  • macOS上:/Users/user/.kivy/config.ini。

  • Android上:/data/data/org.kivy.launcher/files/.kivy/config.ini。

  • iOS上:<HOME_DIRECTORY>/Documents/.kivy/config.ini。

为了更改默认设置,您可以手动更改此文件或使用Config对象。Config对象的read()、set()和write()方法分别用于读取设置的值、分配新值以及将更改写入配置文件。

from kivy.config import Config

Config.read(<file>)

Config.write() # set config

您还可以通过设置环境变量(以编程方式或从操作系统终端)来为任何配置参数(称为令牌)分配仅在当前会话中有效的值。

从Python设置环境变量:

import os
os.environ['KIVY_LOG_MODE'] = MIXED'

您也可以从OS终端设置环境变量。在Windows命令提示符终端中:

set KIVY_LOG_MODE = MIXED'

在Linux/MacOS上:

export KIVY_LOG_MODE = MIXED'

配置文件config.ini包含一个或多个部分,每个部分包含称为令牌的参数。KIvy安装的典型“config.ini”文件包含Kivy、Graphics、widgets等部分。

kivy configuration object

要使用环境变量更改某个配置设置,请使用以下指令格式:

KCFG_<section>_<key> = <value>

例如,要设置日志级别:

KCFG_KIVY_LOG_LEVEL= "warning"

可以使用以下语法以编程方式执行相同的操作:

os.environ["KCFG_KIVY_LOG_LEVEL"] = " warning"

配置令牌

配置文件分为几个部分,每个部分包含令牌或参数。以下是一些按部分顺序排列的重要令牌:

部分 - [Kivy]

  • default_font - 用于显示任何文本的小部件的默认字体。

  • desktop - 整数,0或1。此选项控制功能,例如启用或禁用滚动视图中的可拖动滚动条、禁用TextInput中的气泡等。

  • log_dir - 日志目录的路径。

  • log_level - 字符串,'trace'、'debug'、'info'、'warning'、'error'或'critical'之一。设置要使用的最小日志级别。

部分 - [postproc]

  • double_tap_distance - 双击允许的最大距离,在0-1000范围内归一化。

  • double_tap_time - 双击检测允许的时间,以毫秒为单位。

  • triple_tap_distance - 三击允许的最大距离,在0-1000范围内归一化。

  • triple_tap_time - 三击检测允许的时间,以毫秒为单位。

部分 [graphics]

  • fullscreen - 0、1、'fake'或'auto'之一。

  • height - 窗口的高度,如果fullscreen设置为auto则不使用。

  • left - 窗口的左侧位置。

  • top - 窗口的顶部位置。

  • resizable - 0或1之一 - 0表示固定大小,1表示可调整大小。

  • width - 窗口的宽度,如果fullscreen设置为auto则不使用。

部分 [widgets]

  • scroll_distance - ScrollView小部件使用的scroll_distance属性的默认值。

  • scroll_timeout - ScrollView小部件使用的scroll_timeout属性的默认值。

Config对象的 方法

add_section() - 如果缺少该部分,则向配置中添加一个部分。例如:

Config.add_section('newsection')

get() - 获取给定部分的选项值。例如:

Config.get('graphics', 'width')

set() - 为配置令牌分配值。例如:

Config.set('graphics', 'width', 720)

write() - 将配置写入使用read()方法打开的最后一个文件。如果写入成功完成,则返回True,否则返回False。

Kivy - 图集

如果您的应用程序预计要使用许多图像,尤其是在远程服务器上,则需要优化它们的加载时间。Kivy框架中的Atlas有助于减少加载时间。使用Kivy Atlas,应用程序只需要加载一个图像文件,这减少了错误的可能性,并且比加载多个图像使用更少的资源。Atlas通常用于Kivy游戏应用程序中渲染图像。

Atlas类在“kivy.atlas”模块中可用。此模块可用于以编程方式构建Atlas对象,以及使用其命令行界面。

要能够使用Atlas,当前的Kivy环境必须具有pillow包 - Python的图像库。如果不可用,请使用以下命令安装它:

pip3 install pillow

Atlas由两个文件组成:

  • 一个json文件(扩展名为“.atlas”),其中包含图像文件名和图集的纹理位置。

  • 包含“.atlas”文件引用的纹理的图像文件

假设以下图像位于名为imges的目录中:

C:\kivyenv\images目录

forward.png    pause.png    play.png    previous.png

这些图像中的每一个都是64×64像素的尺寸。因此,我们指定一个256×256像素大小的图集文件来容纳所有四个文件。打开操作系统的命令终端并运行以下命令:

python -m kivy.atlas myatlas 256x256 *.png

终端显示以下日志信息,最后会在images文件夹中创建这两个文件。

(kivyenv) C:\kivyenv\images>python -m kivy.atlas myatlas 256x256 *.png
[INFO  ] [Logger   ] Record log in C:\Users\mlath\.kivy\logs\kivy_23-07-20_33.txt
[INFO  ] [deps     ] Successfully imported "kivy_deps.gstreamer" 0.3.3
[INFO  ] [deps     ] Successfully imported "kivy_deps.angle" 0.3.3
[INFO  ] [deps     ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO  ] [deps     ] Successfully imported "kivy_deps.sdl2" 0.6.0
[INFO  ] [Kivy     ] v2.2.0
[INFO  ] [Kivy     ] Installed at "c:\kivyenv\Lib\sitepackages\kivy\__init__.py"
[INFO  ] [Python   ] v3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
[INFO  ] [Python   ] Interpreter at "c:\kivyenv\Scripts\python.exe"
[INFO  ] [Logger   ] Purge log fired. Processing...
[INFO  ] [Logger   ] Purge finished!
[DEBUG ] STREAM b'IHDR' 16 13
[DEBUG ] STREAM b'sRGB' 41 1
[DEBUG ] STREAM b'gAMA' 54 4
[DEBUG ] STREAM b'pHYs' 70 9
[DEBUG ] STREAM b'IDAT' 91 1115
[DEBUG ] STREAM b'IHDR' 16 13
[DEBUG ] STREAM b'sRGB' 41 1
[DEBUG ] STREAM b'gAMA' 54 4
[DEBUG ] STREAM b'pHYs' 70 9
[DEBUG ] STREAM b'IDAT' 91 990
[DEBUG ] STREAM b'IHDR' 16 13
[DEBUG ] STREAM b'sRGB' 41 1
[DEBUG ] STREAM b'gAMA' 54 4
[DEBUG ] STREAM b'pHYs' 70 9
[DEBUG ] STREAM b'IDAT' 91 1230
[INFO  ] [Atlas   ] create an 256x256 rgba image
Atlas created at myatlas.atlas
1 image has been created

您可以看到创建的两个附加文件是myatlas-0.png,它是一个包含所有图像的256×256文件,以及一个图集文件。

forward.png    myatlas-0.png    myatlas.atlas    pause.png
play.png
previous.png

“.atlas”文件是一个JSON文件,包含组成文件的纹理位置信息。

{"myatlas-0.png": {"forward": [2, 190, 64, 64], "pause": [68, 190, 64, 64], "play": [134, 190, 64, 64], "previous": [2, 124, 64, 64]}}

为了在Kivy语言中使用Atlas,我们必须使用以下格式:

atlas://path/to/atlas/atlas_name/id. 

“id”文件指的是不带扩展名的图像文件名。

例如,如果要将图像文件用作按钮在正常状态下的背景,则可以按如下方式操作:

B1 = Button(background_normal='images/play.png')
Now, aAfter generating the Atlas, you can use the URL
'atlas://images/myatlas/play' as the value.
B1=Button(background_normal='atlas://images/myatlas/play')

请注意,在图集URL中,无需添加.atlas扩展名。它将自动附加到文件名。

示例

让我们使用Atlas中的图像URL来设置应用程序窗口上四个按钮(播放、暂停、快进和后退)的正常背景。

在运行以下代码之前,必须如上所述创建图集文件:

from kivy.app import App
from kivy.graphics import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.core.window import Window
Window.size = (720, 400)

class AtlasApp(App):
   def build(self):
      main = GridLayout(cols=1)
      self.l1 = Label(text='Using Atlas', font_size=32)
      main.add_widget(self.l1)
      root = FloatLayout(size=(Window.width, 100))
      with root.canvas:
         Color(.2, .7, .1, 1)
         Rectangle(pos=root.pos, size=root.size)
      
      self.btn1 = Button(
         size_hint=(None, None),
         pos_hint={'center_x': .2, 'center_y': .2}
      )
      self.btn2 = Button(
         size_hint=(None, None),
         pos_hint={'center_x': .4, 'center_y': .2}
      )
      
      self.btn3 = Button(
         size_hint=(None, None),
         pos_hint={'center_x': .6, 'center_y': .2}
      )
      
      self.btn4 = Button(
         size_hint=(None, None),
         pos_hint={'center_x': .8, 'center_y': .2}
      )
      self.btn1.background_normal = 'atlas://images/myatlas/forward'
      self.btn2.background_normal = 'atlas://images/myatlas/play'
      self.btn3.background_normal = 'atlas://images/myatlas/pause'
      self.btn4.background_normal = 'atlas://images/myatlas/previous'
      
      root.add_widget(self.btn1)
      root.add_widget(self.btn2)
      root.add_widget(self.btn3)
      root.add_widget(self.btn4)
      
      main.add_widget(root)
      return main
AtlasApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Atlas

Kivy - 数据加载器

Kivy框架中的Loader类是一个异步数据加载器,它使即使数据尚未可用也能加载图像成为可能。当您想要从Internet URL加载图像时,此功能特别有用。

Loader类在kivy.loader模块中定义。Loader类的典型用法如下:

from kivy.loader import Loader
image = Loader.image('http://mysite.com/test.png')

使用loading_image属性指定默认图像。

Loader.loading_image = Image(default.png')

Loader类配备了以下属性:

  • error_image - 用于错误的图像。例如:

Loader.error_image = 'error.png'
  • image(filename) - 使用Loader加载图像。返回一个带有加载图像的ProxyImage。

  • loading_image - 用于加载的图像。例如:

Loader.loading_image = 'loading.png'
  • max_upload_per_frame - 每帧上传的图像数量。默认情况下,我们每帧仅将2张图像上传到GPU。

  • num_workers - 加载时要使用的worker数量。此设置仅在初始化时影响加载器。加载器启动后,该设置无效。

from kivy.loader import Loader
Loader.num_workers = 4

默认值为“2”,以提供流畅的用户体验。

  • ProxyImage() - Loader.image()函数返回的图像。

proxyImage = Loader.image("test.jpg")
  • pause() - 暂停加载器。

  • resume() - 在pause()之后恢复加载器。

  • run() - 加载器的主循环。

  • start() - 启动加载器线程/进程。

  • stop() - 停止加载器线程/进程。

当图像加载或更改时,将触发“on_load”事件。类似地,当图像无法加载时,将触发“on_error”。“error: 发生的异常数据”。

示例

在下面给出的代码中,Loader对象从Internet URL加载图像。Loader返回的ProxyImage对象绑定到其on_load事件上的方法。回调方法使用其纹理作为Image对象的texture属性。

from kivy.app import App
from kivy.uix.image import Image
from kivy.loader import Loader
from kivy.core.window import Window

Window.size = (720,400)

class MyApp(App):

   title='Loader'
   
   def _image_loaded(self, proxyImage):
   if proxyImage.image.texture:
      self.image.texture = proxyImage.image.texture
   def build(self):
      proxyImage = Loader.image('https://source.unsplash.com/user/c_v_r/640x480')
      proxyImage.bind(on_load=self._image_loaded)
      self.image = Image()
      return self.image
MyApp().run()

输出

执行时,将产生以下输出:

Kivy Data Loader

Kivy - 缓存管理器

在Kivy框架中,Cache类通过将它们分配为唯一键的值来存储一个或多个Python对象。Kivy的Cache管理器通过对其中的对象施加限制(例如限制对象数量或设置访问超时)来控制它们。

Cache类在“kivy.cache”模块中定义。它包含静态方法:register()、append()、get()等。

首先,您需要通过设置对象的最大数量和超时来在Cache管理器中注册一个类别。

from kivy.cache import Cache
Cache.register(category='mycache', limit=10, timeout=5)

您现在最多可以添加10个Python对象,每个对象都有一个唯一的键。

key = 'objectid'
instance = Label(text=text)
Cache.append('mycache', key, instance)

get()方法从缓存中检索对象。

instance = Cache.get('mycache', key)

Cache类定义了以下方法和属性

register()方法

此方法使用指定的限制在缓存中注册一个新类别。它具有以下参数:

  • category - 类别的字符串标识符。

  • limit - 缓存中允许的对象的最大数量。如果为None,则不应用任何限制。

  • timeout - 对象从缓存中删除之前的时间。

append()方法

此方法将新对象添加到缓存中。定义了以下参数:

  • category - 类别的字符串标识符。

  • key - 要存储的对象的唯一标识符。

  • obj - 要存储在缓存中的对象。

  • timeout - 如果对象未被使用,则删除对象之前的时间。如果将None用作键,则会引发ValueError。

get()方法

此方法用于从缓存中获取对象,具有以下参数:

  • category - 类别的字符串标识符。

  • key - 存储中对象的唯一标识符。

  • default - 如果未找到键,则返回的默认值。

示例

请查看以下示例:

from kivy.cache import Cache
from kivy.uix.button import Button
from kivy.uix.label import Label

Cache.register(category='CacheTest', limit=5, timeout=15)

b1 = Button(text='Button Cache Test')
Cache.append(category='CacheTest', key='Button', obj=b1)

l1 = Label(text='Label Cache Test')
Cache.append(category='CacheTest', key='Label', obj=l1)

ret = (Cache.get('CacheTest', 'Label').text)
print (ret)

输出

它将产生以下输出 -

Label Cache Test

Kivy - 控制台

Kivy 中的 Console 工具类似于 Inspector 工具,但额外提供了一个功能,可以通过其插件架构添加按钮和面板。与 Inspector 一样,“kivy.modules.console” 模块具有命令行界面,以及用于以编程方式使用该工具的 API。

在命令行使用时,使用 Python 可执行文件的“-m”选项加载模块。

python main.py -m console

要激活 Kivy Console,请使用 Window 和 App 对象作为参数,从 console 模块调用 create_console() 函数。您需要将以下语句放在 App 类的 build() 方法中。

from kivy.modules import console

class Demo(App):
   def build(self):
      button = Button(text="Test")
      console.create_console(Window, self)
      return button

假设您已经开发了一个名为“slider.py”的 Kivy 应用程序,其界面如下所示。

Kivy Console Slider

该应用程序具有三个滑块控件,可帮助更改上面文本的颜色。在 slider.py 文件的 build() 方法中添加对 create_console() 函数的调用(如上所述)并运行它。首先,将显示上述界面。按 Ctrl+E 激活 Console 工具。

定义了以下按键组合:

  • “Ctrl + e” - 切换控制台

  • “Escape” - 取消窗口小部件查找,然后隐藏 Inspector 视图

  • “Up” - 选择父窗口小部件

  • “Down” - 选择当前选定窗口小部件的第一个子窗口小部件

  • “Left” - 选择前一个同级窗口小部件

  • “Right” - 选择下一个同级窗口小部件

激活 Console 工具后,它会在中间显示一个栏,左侧有“Select”、“Properties”和“Tree”按钮,右侧有“FPS”按钮。

Kivy Console Fps Button

单击“Tree”按钮,您将获得界面的窗口小部件树。从树中选择 Slider 控件。

Kivy Console Tree Button

现在按下“Properties”按钮。将显示 Slider 窗口小部件的所有属性的可滚动列表。

Kivy Console Properties Button

向下滚动到 value 属性,然后双击以获取一个可编辑框,其中显示当前值,您可以更改该值。

Kivy Console Property Value

按 Escape 移除控制台。您应该会看到 Slider 位于您从控制台设置的值处。

Kivy Console Slider Value

插件

Console 工具的重要功能之一是插件架构,它允许您向其中添加按钮和面板。默认情况下激活的插件有:

  • ConsoleAddonFps - 在右上角显示 FPS。

  • ConsoleAddonSelect - 激活选择模式。

  • ConsoleAddonBreadcrumb - 在底部显示当前窗口小部件的层次结构。

  • ConsoleAddonWidgetTree - 用于显示应用程序窗口小部件树的面板。

  • ConsoleAddonWidgetPanel - 用于显示所选窗口小部件属性的面板。

要激活插件,必须在调用 create_console 之前将其添加到 Console.addons 中。

关于插件

让我们在 Console 工具中添加一个“About”按钮。它在控制台工具工作区中间显示对象描述,包括其 ID。

在 slider.py 中,在 App 类定义之前添加以下代码:

from kivy.modules.console import Console, ConsoleAddon, ConsoleLabel

class ConsoleAddonAbout(ConsoleAddon):
   def init(self):
      self.console.add_panel(
         "About", self.panel_activate,
         self.panel_deactivate
      )
   def panel_activate(self):
      self.console.bind(widget=self.update_content)
      self.update_content()
   def panel_deactivate(self):
      self.console.unbind(widget=self.update_content)
   def deactivate(self):
      self.panel_deactivate()
   def update_content(self, *args):
      widget = self.console.widget
      if not widget:
         return
      text = "Selected widget is: {!r}".format(widget)
      lbl = ConsoleLabel(text=text)
      self.console.set_content(lbl)
   
Console.register_addon(ConsoleAddonAbout)

如果您再次运行 slider.py 脚本,您将看到“About”按钮添加到“Tree”按钮旁边。从窗口小部件树中找到 Slider,然后单击“About”按钮以显示 Slider 窗口小部件的 Object ID。

Kivy Console About Button

Kivy - 动画

将动画效果应用于窗口小部件是 Kivy 框架最吸引人的功能之一。在 Kivy 中,动画功能在 kivy.animation 模块中包含的 Animation 类中定义。同一个模块还包含 AnimationTransition 类。它是一组用于应用动画过渡效果的函数。

要使用动画效果,Kivy 应用程序应:

  • 首先确定要为哪个窗口小部件属性设置动画,

  • 设置 Animation 类的对象,

  • 设置属性的最终值,

  • 调用 Animation 对象的 start() 方法,并将窗口小部件作为参数传递。

要为窗口小部件的 x 或 y 位置设置动画,只需指定在动画结束时希望窗口小部件位于其中的目标 x/y 值即可:

rom kivy.animation import Animation
anim = Animation(x=100, y=100)
anim.start(widget)

这将为窗口小部件的 x 和 y 位置设置动画,从其原始位置到 100,100,默认持续时间为一秒。

您可以要求为多个属性设置动画。例如,要为位置和大小设置动画,请使用:

anim = Animation(x=50, size=(80, 80))
anim.start(widget)

您还可以指定 Animation 对象的 transition(或 t)属性以应用过渡效果。

在两个 Animation 对象之间使用“+”运算符将动画按顺序连接起来。

anim = Animation(x=50) + Animation(size=(80, 80), duration=2)

这将为窗口小部件的 x=50 设置动画,持续 1 秒,然后为大小设置动画到 (80, 80),持续接下来的 2 秒。

另一方面,“&”运算符并行连接动画。以下示例将在 1 秒内同时为位置设置动画到 (80, 10),为大小设置动画到 (800, 800):

anim = Animation(pos=(80, 10))
anim &= Animation(size=(800, 800), duration=2.)

如果程序正在实现“顺序”动画,可以通过设置 anim.repeat = True 使动画循环重复。

Animation 类具有以下属性和方法:

  • duration 或 d - 动画持续时间(以秒为单位),默认为 1。

  • transition 或 t - 用于为属性设置动画的过渡函数。它可以是 AnimationTransition 中方法的名称。

  • step 或 s - 浮点数。动画的毫秒步长。默认为 0,这意味着动画将为每一帧更新。如果要以 30 FPS 设置动画,请使用 s=1/30。

  • cancel() - 取消之前应用于窗口小部件的动画。

  • cancel_all() - 取消所有与特定窗口小部件/属性列表相关的动画。

  • start() - 在窗口小部件上启动动画。

  • stop() - 停止之前应用于窗口小部件的动画,触发 on_complete 事件。

  • stop_all() - 停止所有与特定窗口小部件/属性列表相关的动画。

示例

借助以下“kv”脚本,按钮和标签被放置在垂直框布局中。按钮绑定到animating()方法。

<MyLayout>
   BoxLayout:
      orientation: "vertical"
      size: root.width, root.height
      padding: 50
      spacing: 20
      Label:
         id: my_label
         text: "Hello World!"
         font_size: 32
      Button:
         text: "Start"
         font_size: 32
         size_hint: .5, .5
         pos_hint: {"center_x": 0.5}
         on_release: root.animating(self)

此“kv”脚本加载到 App 类中。animating() 方法在单击按钮时对按钮应用位置、背景颜色和大小动画效果。

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.animation import Animation
from kivy.core.window import Window

Window.size = (720, 400)

class MyLayout(Widget):
   def animating(self, widget, *args):
      animate = Animation(
         pos=(widget.pos[0], Window.height - 50)
      )
      animate += Animation(pos=(widget.pos[0], 0))
      animate += Animation(
         background_color=(0, 0, 1, 1),
         duration=1
      )
      animate += Animation(size_hint=(1, 1))
      animate += Animation(size_hint=(.5, .5))
      animate += Animation(pos_hint={"center_x": 0.1})
      animate += Animation(pos_hint={"center_x": 0.5})
      animate.start(widget)
      
      # Create a callback
      animate.bind(on_complete=self.my_callback)
   
   def my_callback(self, *args):
      self.ids.my_label.text = "Hello Kivy"

class AwesomeApp(App):
   def build(self):
      return MyLayout()

if __name__ == '__main__':
   AwesomeApp().run()

输出

运行上述程序。您应该会看到一个标签及其下方的按钮。

Kivy Animation

单击按钮以启动动画效果。按钮上下移动、左右移动、大小增大和缩小,以及颜色变化。

Kivy Animation Start

Kivy - 多笔画

Kivy 中的多笔划手势是一组多个手势对象。手势构成一个单笔划,由“touch_down”和“touch_up”事件之间的触摸点列表构成。多笔划手势是此类笔划的集合。

“kivy.multistroke”模块实现了量角器手势识别算法。在此模块中定义的两个重要类是MultistrokeGestureRecognizer

MultistrokeGesture 类维护一组笔划,并生成用于稍后针对此手势评估候选者的单笔划(即 UnistrokeTemplate)排列。

可以使用以下语法获得 MultriStroke 对象:

from kivy.vector import Vector
from kivy.multistroke import MultistrokeGesture

gesture = MultistrokeGesture('my_gesture', strokes=[
   [Vector(x1, y1), Vector(x2, y2), ...... ], # stroke 1
   [Vector(), Vector(), Vector(), Vector() ] # stroke 2
   #, [stroke 3], [stroke 4], ...
])

即使所有笔划都组合到单个列表(单笔划)中,您仍然应该分别指定笔划,并将 stroke_sensitive 属性设置为 True。

Recognizer 存储 MultistrokeGesture 对象列表。它类似于 GestureDatabase 的搜索/数据库 API。它维护一个列表,并允许您在其中搜索用户输入的手势。

Recognizer 数据库是 UnistrokeTemplate 对象的容器,并实现堆置换算法以自动生成所有可能的笔划顺序。

Candidate 类的对象表示一组用户输入的单笔划路径。此对象由调用 Recognizer.recognize() 自动实例化。

from kivy.vector import Vector
from kivy.multistroke import Recognizer

gdb = Recognizer()
gdb.recognize([
   [Vector(x1, y1), Vector(x2, y2)],
   [Vector(x3, y3), Vector(x4, y4)]])

Recognizer 对象能够生成以下事件:

  • on_search_start - 使用此 Recognizer 启动新搜索时触发。

  • on_search_complete - 正在运行的搜索以任何原因结束时触发。

这些事件可以映射到回调,以跟踪 Recognizer.recognize() 方法执行的搜索操作的进度。

gdb.bind(on_search_start=search_start)
gdb.bind(on_search_complete=search_stop)

回调方法的示例如下:

def search_start(gdb, pt):
   print("A search is starting with {} tasks".format(pt.tasks))
   
def search_stop(gdb, pt):
   best = pt.best
   print("Search ended {}. Best is {} (score {}, distance {})".format(
      pt.status, best['name'], best['score'], best['dist'] ))

最后,“kivy.multistroke”模块还提供了一个 ProgressTracker 类。它表示正在进行(或已完成)的搜索操作。

跟踪器对象由 Recognizer.recognize() 方法在调用时自动实例化并返回。results 属性是一个字典,在识别操作进行时会更新该字典。

progress = gdb.recognize([
   [Vector(x1, y1), Vector(x2, y2)],
   [Vector(x3, y3), Vector(x4, y4)]])
   
progress.bind(on_progress=my_other_callback)
print(progress.progress)      # = 0
print(result.progress)        # = 1

您可以使用 export_gesture() 函数将多笔划手势保存到文件中。

  • export_gesture(filename=None) - 它将 MultistrokeGesture 对象列表导出到一个 Base64 编码的字符串,该字符串可以使用 parse_gesture() 函数解码为 Python 列表。它也可以使用 Recognizer.import_gesture() 直接导入到数据库中。如果指定了 filename,则输出将写入磁盘。

  • import_gesture(data=None,filename=None) - import_gesture() 函数将手势带入 Recognizer 数据库。使用此函数导入由 export_gesture() 格式化的手势列表。必须指定 data 或 filename 参数。此方法接受可选的 Recognizer.filter() 参数,如果没有指定参数,则导入指定数据中的所有手势。

Kivy - 时钟

Kivy 框架中的 Clock 对象是一个全局事件调度器。它用于在特定时间间隔内计划和触发事件。要计划重复或一次性发生的事件,它会绑定到一个回调函数。您可以通过“dt”参数获取计划和调用回调之间经过的时间,该参数是时间增量。

Clock 对象在“kivy.clock”模块中定义。它包含诸如“schedule_once()”和“schedule_interval()”之类的方法,以注册要在一定延迟后或以定期间隔调用的函数或方法。此机制可用于处理计时事件、动画更新以及应用程序中的其他重复任务。

“schedule_interval()”和“schedule_once()”这两个函数都有两个参数;一个回调和以秒为单位的时间间隔。

from kivy.clock import Clock

def clock_callback(dt):
   "Clock event occurred"
   
# call clock_callback every 2 seconds
Clock.schedule_interval(clock_callback, 2)

# call clock_callback in 5 seconds
Clock.schedule_once(clock_callback, 5)

timeout dt 参数的默认值为 0。因此,要尽快调用回调函数,请将第二个参数设置为计划事件为 0,或者通过不使用参数让默认值被使用。

Clock.schedule_once(my_callback)

使用“-1”的超时会导致事件在下一帧之前发生,将其设置为 0 会导致事件在下一帧之后发生。

触发的事件

Clock 对象可以使用以下函数触发时钟事件:

  • schedule_interval(callback, timeout) - 此函数计划每隔指定的秒数调用一次事件。该函数返回一个 ClockEvent 对象。在返回的事件上调用 ClockEvent.cancel() 以取消计划事件。

  • schedule_once(callback, timeout=0) - 此函数计划一次执行事件,并在指定的秒数后执行,并返回 ClockEvent 对象。如果未指定 timeout 或为 0,则回调将在渲染下一帧后调用。要取消事件,请在返回的事件上调用 ClockEvent.cancel()。

  • create_trigger(callback, timeout=0) - 此函数创建 Trigger 事件。与其他两个函数不同,事件不会自动计划,您需要调用它。与其他两个一样,在事件执行之前取消它,方法是调用 ClockEvent.cancel()。要再次计划它,只需调用事件(event())。

create_trigger() 函数具有以下参数:

  • callback - 要执行的回调。来自 kivy。它采用 timeout 参数来指定在调用回调之前等待多长时间。

  • interval − 一个布尔参数,指示回调函数是否应该调用一次 (False) 或者重复调用。

from kivy.clock import Clock
def clock_callback(dt):
   "Clock event occurred"
triggerevent = Clock.create_trigger(clock_callback, 5)
triggerevent()

要取消使用以上两种方法创建的事件,可以使用 event.cancel() 或 event.unschedule() 方法。

示例

下面给出的代码在 Kivy 应用程序窗口上运行一个倒计时计时器。“kv”脚本在一个单列网格布局中放置了一个 TextInput 框、一个标签和一个按钮。

<clockwidget>:
   GridLayout:
      cols:1
      size:root.size
      TextInput :
         font_size : '30pt'
         id:t1
         halign:'center'
         valign:'center'
      Label:
         id: l1
         text : 'Current Value: '
         font_size : '20pt'
      
      Button :
         id:b1
         text : 'Start Countdown'
         font_size:20

布局类 clockwidget 继承 GridLayout 并将命令按钮绑定到一个方法,该方法安排每隔一秒发生一次的周期性事件。

每次调用回调函数时,标签都会显示递减的数字,从用户在文本框中输入的值开始。当它达到 0 时,事件将通过其 cancel() 方法取消调度。

class clockwidget(GridLayout):
   def __init__(self, *args):
      super(*args).__init__()
      self.ids.b1.bind(on_press=self.showtime)

   def countdown(self, dt):
      if self.val==0:
         self.ids.l1.text="Countdown Stopped"
         self.ids.l1.color=[1,0,0]
         self.ev.cancel()
         self.ids.b1.disabled=False
      else:
         self.ids.l1.text="Current Value: {}".format(self.val)
         self.ids.l1.color=[1,1,1]
         self.val=self.val-1
         
   def showtime(self, *args):
      self.val=int(self.ids.t1.text)
      self.ev=Clock.schedule_interval(self.countdown, 1)
      self.ids.b1.disabled=True

本练习的完整代码如下所示:

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.clock import Clock
from kivy.core.window import Window

Window.size = (720, 400)

class clockwidget(GridLayout):
   def __init__(self, *args):
      super(*args).__init__()
      self.ids.b1.bind(on_press=self.showtime)
      
   def countdown(self, dt):
      if self.val == 0:
         self.ids.l1.text = "Countdown Stopped"
         self.ids.l1.color = [1, 0, 0]
         self.ev.cancel()
         self.ids.b1.disabled = False
      else:
         self.ids.l1.text = "Current Value: {}".format(self.val)
         self.ids.l1.color = [1, 1, 1]
         self.val = self.val - 1
   def showtime(self, *args):
      self.val = int(self.ids.t1.text)
      self.ev = Clock.schedule_interval(self.countdown, 1)
      self.ids.b1.disabled = True
      
class clockdemoapp(App):
   def build(self):
      w = clockwidget()
      w.cols = 1
      return w
      
clockdemoapp().run()

输出

运行此程序时,它将显示以下应用程序窗口。输入倒计时计时器的值,然后单击“开始”按钮。

Kivy Clock

标签开始更新,递减倒计时值的数字。

Kivy Clock Start

当它达到“0”时,按钮将再次启用。

Kivy Clock Stop

Kivy - SVG

Kivy 框架支持显示 SVG 文件,尽管目前仍处于高度实验阶段。在计算机图形学中,SVG 代表可缩放矢量图形,这是 W3C 制定的用于编码图像数据的标准。

诸如 PNG 和 JPG 之类的图像格式基于光栅图形,其中图像数据以位图的形式存储,位图是像素的颜色和位置的网格。这种格式的缺点是,如果图像被放大,则图像在达到一定程度后会开始变得模糊。

另一方面,矢量图形图像以一系列 XML 指令的形式以数学方式存储,这些指令用于在屏幕上绘制图像。这些指令告诉查看程序如何在屏幕上“绘制”图像。绘制可以在任何尺寸进行,因为 SVG 文件与分辨率无关。它们可以放大或缩小,而不会降低质量或清晰度。

Kivy 库在“kivy.graphics.svg”模块中定义了 Svg 类。要在任何小部件的画布上绘制 SVG 图像,我们可以使用以下语法:

from kivy.graphics.svg import Svg
with widget.canvas:
   svg = Svg("test.svg")

Svg 类具有以下属性:

  • anchor_x − 用于缩放和旋转的水平锚点位置。默认为 0。值 0、1 和 2 分别对应于“左”、“中”和“右”。

  • anchor_y − 用于缩放和旋转的垂直锚点位置。默认为 0。值 0、1 和 2 分别对应于“左”、“中”和“右”。

  • color − 用于指定“currentColor”的 SvgElements 的默认颜色。

  • height − 'double'

  • source − 要加载的 SVG 文件名/源。

  • width − 'double'

示例

以下程序使用“kv”脚本加载 Scatter 小部件。一个“svg”对象放置在一个 GridLayout 中。将文件名作为其 source 属性。以下是“kv”文件:

<SvgWidget>:
   do_rotation: True
<FloatLayout>:
   canvas.before:
      Color:
         rgb: (1, 1, 1)
      Rectangle:
         pos: self.pos
         size: self.size

Kivy App 类的 Python 代码:

from kivy.uix.scatter import Scatter
from kivy.app import App
from kivy.graphics.svg import Svg
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.core.window import Window

Window.size = (720,400)

class SvgWidget(Scatter):
   def __init__(self, filename):
      super(SvgWidget, self).__init__()
      with self.canvas:
         svg = Svg(source=filename)
      self.size = Window.width, Window.height
      
class SvgDemoApp(App):
   def build(self):
      self.root = GridLayout(cols=1)
      
      filename = "ship.svg"
      svg = SvgWidget(filename)

      self.root.add_widget(svg)
      svg.scale = 4

SvgDemoApp().run()

输出

运行此程序时,它将生成以下输出窗口:

Kivy Svgs

Kivy - UrlRequest

Kivy 框架中的 UrlRequest 类允许您在网络上发出异步请求,并在请求完成后获取结果。其功能与 JavaScript 中的 XHR 对象相同。

UrlRequest 类在“kivy.network.urlrequest”模块中定义。构造函数只需要一个名为“url”的必填参数。该类继承了 Python 的 threading 模块中的 Thread 类。

UrlRequest 构造函数的参数为:

  • url − 表示要调用的完整 url 字符串的字符串。

  • on_success − 获取结果后要调用的回调函数。

  • on_redirect − 如果服务器返回重定向,则要调用的回调函数。

  • on_failure − 如果服务器返回客户端或服务器错误,则要调用的回调函数。

  • on_error − 如果发生错误,则要调用的回调函数。

  • on_progress − 将被调用的回调函数,用于报告下载的进度。

  • on_cancel − 如果用户通过 .cancel() 方法请求取消下载操作,则要调用的回调函数。

  • on_finish − 如果请求完成,则要调用的附加回调函数。

  • req_body − 表示要与请求一起发送的数据的字符串。如果包含此参数,则将执行 POST 操作。否则,将发送 GET 请求。

  • req_headers − dict,默认为 None。要添加到请求的自定义标头。

  • chunk_size − 要读取的每个块的大小,仅在设置了 on_progress 回调时使用。

  • timeout − 一个整数,如果设置,阻塞操作将在这么多秒后超时。

  • method − 要使用的 HTTP 方法,默认为“GET”(如果指定了 body,则为“POST”)。

  • decode − bool,默认为 True。如果为 False,则跳过响应的解码。

  • debug − bool,默认为 False。

  • auth − HTTPBasicAuth,默认为 None。如果设置,请求将使用基本身份验证进行身份验证。

UrlRequest 对象的 cancel() 方法取消当前请求,并将调用回调 on_cancel。

示例

首先,我们设计一个用户界面,其中包含一个包含 httpbin.org URL(带变量参数)的微调器,两个只读文本框 - 一个显示获取的 URL 的结果,另一个显示响应标头的 JSON 字符串。在它们之间放置了一个 Image 小部件。如果 content_type 为图像类型,它将显示图像。这些小部件放置在一个垂直的 Box 布局中。

布局使用以下 kv 语言脚本构建。

#:import json json

BoxLayout:
   orientation: 'vertical'
   TextInput:
      id: ti
      hint_text: 'type url or select from dropdown'
      size_hint_y: None
      height: 48
      multiline: False
   BoxLayout:
      size_hint_y: None
      height: 48
      Spinner:
         id: spinner
         text: 'select'
         values:
            [
               'http://httpbin.org/ip',
               'http://httpbin.org/headers',
               'http://httpbin.org/delay/3',
               'https://httpbin.org/image/png',
            ]
         on_text: ti.text = self.text
      Button:
         text: 'GET'
         on_press: app.fetch_content(ti.text)
         size_hint_x: None
         width: 50
   TextInput:
      readonly: True
      text: app.result_text
   Image:
      source: app.result_image
      nocache: True
   TextInput
      readonly: True
      text: json.dumps(app.headers, indent=2)

Kivy App 类代码如下所示。它基本上向 httpbin.org 发送不同的 HTTP 请求。httpbin.org 是一种简单的 HTTP 请求和响应服务。应用程序将 UrlRequest 的结果存储在三个字符串变量中,并在小部件中显示。

从下拉列表中选择 URL 并按下 GET 按钮后,它将调用 fetch_content() 方法并收集响应。

如果回复标头包含具有图像的 content_type,则 Image 小部件将加载图像。

from kivy.lang import Builder
from kivy.app import App
from kivy.network.urlrequest import UrlRequest
from kivy.properties import NumericProperty, StringProperty, DictProperty
import json
from kivy.core.window import Window

Window.size = (720,400)

class UrlExample(App):
   status = NumericProperty()
   result_text = StringProperty()
   result_image = StringProperty()
   headers = DictProperty()

   def build(self):
      pass

   def fetch_content(self, url):
      self.cleanup()
      UrlRequest(
         url,
         on_success=self.on_success,
         on_failure=self.on_failure,
         on_error=self.on_error
      )
   
   def cleanup(self):
      self.result_text = ''
      self.result_image = ''
      self.status = 0
      self.headers = {}
   
   def on_success(self, req, result):
      self.cleanup()
      headers = req.resp_headers
      content_type = headers.get('content-type', headers.get('Content-Type'))
      if content_type.startswith('image/'):
         fn = 'tmpfile.{}'.format(content_type.split('/')[1])
         with open(fn, 'wb') as f:
            f.write(result)
         self.result_image = fn
      else:
         if isinstance(result, dict):
            self.result_text = json.dumps(result, indent=2)
         else:
            self.result_text = result
            self.status = req.resp_status
            self.headers = headers
   
   def on_failure(self, req, result):
      self.cleanup()
      self.result_text = result
      self.status = req.resp_status
      self.headers = req.resp_headers
   
   def on_error(self, req, result):
      self.cleanup()
      self.result_text = str(result)
      
UrlExample().run()

输出

运行上述代码。从微调器下拉列表中选择'http://httpbin.org/headers',然后按 GET 按钮。您应该在文本框中看到响应标头。

Kivy UrlRequest

选择'https://httpbin.org/image/png' URL。图像将如下所示显示。

Kivy UrlRequest Image

微调器下拉列表中的一个选项是指向客户端 IP 地址的 URL。从列表中选择'http://httpbin.org/ip'。IP 地址将如下所示显示:

kivy ip address

Kivy - 剪贴板

Kivy 框架中的 Clipboard 对象可以访问正在使用的操作系统的剪贴板。借助 Kivy 的 Clipboard 对象,可以以编程方式执行剪切、复制和粘贴操作。

剪贴板是计算机 RAM 中的临时缓冲区,大多数操作系统都提供该缓冲区用于在应用程序之间和内部进行短期存储和传输。在操作系统中,此剪贴板是一个全局对象。大多数操作系统都使用常规的键盘快捷键在应用程序之间执行剪切、复制和粘贴数据。

通常,不需要通过剪贴板显式使用剪切-复制-粘贴操作。但是,在某些情况下它可能很有用。

Clipboard 对象在“kivy.core.clipboard”模块中定义。Clipboard 对象可以使用以下方法:

  • copy() − 将参数数据中提供的值复制到当前剪贴板。如果数据不是字符串类型,它将被转换为字符串。

  • get() − 获取剪贴板中的当前数据,如果可能,使用 mimetype。不要直接使用此方法。请改用 paste 方法。

  • get_types() − 返回支持的 mimetype 列表。

  • paste() − 从系统剪贴板获取文本并将其返回为可用的字符串。

  • put() − 将数据放入剪贴板,并附加 mimetype。不要直接使用此方法。请改用 copy 方法。

示例

在以下示例中,我们有两个多行文本框和两个按钮,它们排列在一个 BoxLayout 中。COPY 按钮调用 gettext() 方法,该方法将上文本框中选定的文本复制到剪贴板。

def gettext(self, instance):
   Clipboard.copy(data=self.text1.selection_text)

PASTE 按钮调用回调 insert(),该回调将在光标位置粘贴选定的文本。

def insert(self, instance):
   txt = Clipboard.paste()
   print (txt)
   self.text2.insert_text(txt)

这两个函数绑定到两个按钮:-

self.b1=Button(text='COPY')
self.b1.bind(on_press=self.gettext)
self.b2=Button(text='PASTE')
self.b2.bind(on_press=self.insert)

build() 方法用于组装文本框和按钮。

本练习的完整代码如下:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.clipboard import Clipboard
from kivy.core.window import Window

Window.size = (720, 400)

class mydemoapp(App):
   def gettext(self, instance):
      Clipboard.copy(data=self.text1.selection_text)
      
   def insert(self, instance):
      txt = Clipboard.paste()
      print(txt)
      self.text2.insert_text(txt)
      
   def build(self):
      main = BoxLayout(orientation='vertical')
      self.text1 = TextInput(multiline=True, font_size=32)
      btns = BoxLayout(orientation='horizontal')
      self.b1 = Button(text='COPY')
      self.b1.bind(on_press=self.gettext)
      self.b2 = Button(text='PASTE')
      self.b2.bind(on_press=self.insert)
      self.text2 = TextInput(
         multiline=True, font_size=32,
         foreground_color=[0, 0, 1, 1]
      )
      btns.add_widget(self.b1)
      btns.add_widget(self.b2)
      main.add_widget(self.text1)
      main.add_widget(btns)
      main.add_widget(self.text2)
      return main
      
mydemoapp().run()

输出

运行程序时,您将看到两个文本框。在上框中输入Simple is better than Complex,在下框中输入Complex is Complicated

然后,选择子字符串better than,然后单击 COPY 按钮将其存储到剪贴板。在下框中单击“Complicated”一词旁边,然后单击 PASTE 按钮。剪贴板上的文本将被插入。

Kivy Clipboard

Kivy - 工厂

Kivy 中的 factory 类用于自动注册任何类或模块,并在项目的任何位置实例化其中的类。Factory 类在“kivy.factory”模块中定义。

工厂模式是面向对象编程中的软件架构模式。工厂是用于创建其他对象的工具。它是一个函数或方法,它从某些方法调用返回对象或类,返回“新”对象的函数或方法可以称为“工厂”,如工厂方法或工厂函数。

“kivy.factory.Factory”类创建类的实例并将它们添加到窗口部件树中。窗口部件树控制用户界面上的元素。

以下是如何使用 Factory 注册自定义按钮类的示例。

from kivy.factory import Factory
from kivy.uix.button import Button

Factory.register('MyCustomButton', cls=Button)

btn = MyCustomButton( text: "Click me")

类似地,您可以使用 Factory 创建一个类:

from kivy.factory import Factory
from kivy.uix.label import Label

class MyLabel(Label):
   pass

Factory.register('MyLabel', cls=MyLabel)
lbl = MyLabel(text: "Hello world")

默认情况下,通过工厂注册的第一个类名是永久的。如果要更改注册的类,则需要在重新分配它之前取消注册类名。

from kivy.factory import Factory
Factory.register('NewWidget', cls=NewWidget)
widget = Factory.NewWidget()
Factory.unregister('NewWidget')
Factory.register('NewWidget', cls=CustomWidget)
customWidget = Factory.NewWidget()

示例

以下 Kivy 应用程序使用 Factory 注册 MyPopup 类,该类是 Kivy 库中的 Popup 小部件。

Kivy App 类代码如下:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.core.window import Window

Window.size = (720,400)

Builder.load_file('popup.kv')

class MyLayout(Widget):
   pass

class FactorydemoApp(App):
   def build(self):
      return MyLayout()
      
FactorydemoApp().run()

为了填充应用程序窗口,请使用以下“kv”脚本 (popup.kv)

#:import Factory kivy.factory.Factory

<MyPopup@Popup>
   auto_dismiss: False
   size_hint: 0.6, 0.4
   pos_hint: {"x":0.2, "top": 0.9}
   
   title: "Popup Box"
   
   BoxLayout:
      orientation: "vertical"
      size: root.width, root.height
      Label:
         text: "Hello Kivy"
         font_size: 24
      Button:
         text: "Close"
         font_size: 24
         on_release: root.dismiss()

<MyLayout>
   BoxLayout:
      orientation: "vertical"
      size: root.width, root.height
   
      Label:
         text: "Factory Example"
         font_size: 32
      Button:
         text: "Click here"
         font_size: 32
         on_release: Factory.MyPopup().open()

如您所见,MyPopup 类在 Factory 中注册,并且当单击按钮时会调用其 open() 方法。

输出

运行程序以显示带有“单击此处”按钮的窗口。单击它时,弹出窗口将出现在应用程序窗口上。

Kivy Factory

Kivy - 手势

Kivy 框架能够记录和识别手势。手势是由鼠标指针或手指在多点触控设备上生成的一系列触摸操作。kivy.gesture 模块定义了 Gesture 类,其对象由在 Kivy 画布上捕获的连续触摸事件的 (x,y) 坐标获得。Kivy 中的手势是 Oleg Dopertchouk 手势识别算法的 Python 实现。

同一个模块还包含 GestureDatabase 类。您可以在手势数据库中存储一个或多个 Gesture 对象,并找出某个手势是否与数据库中已存储的任何手势匹配。

要创建 Gesture 对象,您需要一个 x,y 坐标列表。例如 -

from kivy.gesture import Gesture
g = Gesture()
g.add_stroke(point_list=[(1,1), (3,4), (2,1)])
g.normalize()

add_stroke() 方法根据坐标对构建手势对象。normalize() 方法用于运行手势归一化算法并计算与自身的点积。

此类 Gesture 对象存储在 GestureDatabase 中。

from kivy.gesture import Gesture, GestureDatabase

# Create a gesture
gdb = GestureDatabase()
gdb.add_gesture(g)

您可以针对存储在此数据库中的对象比较另一个特定对象,并查找数据库中的任何手势是否匹配。

g2 = Gesture()
# ...
gdb.find(g2)

kivy.gesture 模块在 Gesture 类中定义了以下方法 -

  • add_stroke() - 从触摸点列表构造手势的笔画,并返回 Stroke 实例。

  • normalize() - 运行手势归一化算法并计算与自身的点积。

  • get_score() - 当一个手势与另一个手势匹配时,此方法返回匹配分数。

GestureDatabase 类具有以下重要方法 -

  • add_gesture() - 向数据库添加一个新的手势。

  • find() - 在数据库中查找匹配的手势。您可以使用 min_score 参数定义查找的精度。它应该在 0 到 1 之间。

  • gesture_to_str(gesture) - 将手势转换为唯一的字符串。

  • str_to_gesture(data) - 将唯一的字符串转换为手势。

示例

我们定义 touch_down()、touch_move() 和 touch_up() 处理程序来捕获触摸点并从中绘制图案。所有带 (touch.x 和 touch.y) 坐标的点都收集在一个列表中。

按下“添加”按钮时,该列表用于构造一个 Gesture。gesture_to_string() 方法返回一个二进制字符串。

if instance.text=='Add':
   g = Gesture()
   g.add_stroke(point_list=Drawgesture.points)
   g.normalize()
   print (self.d.gdb.gesture_to_str(g))
   self.d.gdb.add_gesture(g)
   print (str(g))

手势字符串的示例可能如下所示 -

b'eNprYJmayc4ABj082ZlllXrpqcUlpUWpU3rY3aGsyVM0G6fUTtHoYS3PTCnJmOLuYO9kuU766IwetozUzPSMEqCIC9NEhiUOGj38UO3xBUX5KaXJICmhWZ/F3Pse9LAXlxTlZ6cWT4mdksHQwws1PRgsiLCDrSA/M68EpEgDqIoHqioAJIhQxFgxxX3/LdkuHrnEhh7Gyinu9g9vmvlOTnlRmpQhCFGTIQJXkSHqbn9/U85stZMXcMrfxiZ/TfZI/b2QH8TIXydH/pLsv8/zPDJA8pfJkT9jU3RuT/kBYuTPp4ACaAGq/AmbtU412Qo45Q/YKmn+CRIAyR+nUP4wWD4BVX5DtZ7Sj8IHIPltJ4EeUHdAlY9n/VPH/4ABJL92MtAAvwaS5O3n8Z6ZJZ8Gkt9fDLK/hwGn/CJQ8G1E078eZP5TB5D8RlDyunEAp/xOkPxNNPO3N3WGd3CD/Lf/AND4TTlo5u9vEingUAHLnwDLo4aP/eED54+4yH3AKX/8wNSAFu0JIPkzYHnU8Lc/fSDqzhELUPzuvwBynpkBqvz5AwqZLC4LQPJXwPKo4W9/8f6nX4s0iJK/hk3+6v0dbY9MNUDyNyiUvwNzf2oPT3FyUWpqHqKccHdIcNSwvsgQ4+5QGrZn4XqNnLYpyGJOuwTWtWijiultr197/w2qGNum2DXTs8FiE3XfGfUrYRcrubfWerXfa7DYQ+MFU2RfAsW2rZBcxQZWl2hoGfR1zXocYn2Lvq/Y+wosFmmjo1YijCq20vFeB9NNoFja3KvLS7NQxYJmuyy7qAkWu+iyfccpW6CY3YzNy3Qgen+6T3g5cQFQTGua0tKOVSCxJE9fZ2+FdKCY2OSJS55kgsUKA2Sqn59ydyh+15e/ePZLVLFb3fcWfV8JFpsJcrIuUOxYp++i4ExUsU1toIAGix0MPXe3bCJQbF6L9kKuF2CxlxEr+Gy/AMXK6jnnH8oAiSULRjfas4ajilnGReWf2Q0US6qpmC+nDhZLTAQGqhxQzK/y+bzKF6hiVuVhc6+uAIt1pvBcjG4EiqmVHJ1rmA4W25j2jEnpKQ4xoSKTOb3qKGJF/4BB8OI5SCyFMWdG8sbVOMRe5QrNdlmOKnYtq3HWArAdKZr5hVMq+ZHEUkuTEns4S/JzUosS85JTgTXUzpkgMKs0SQ8Ayq8zuw=='

您可以添加任意数量的手势。

Kivy Gesture

然后,在画布上绘制一个图案,并尝试找出是否有任何手势与此图案匹配。当按下“查找”按钮时,find() 方法会执行此操作。

if instance.text=='Find':
   g=Gesture()
   g.add_stroke(point_list=Drawgesture.points)
   g.normalize()
   g1=self.d.gdb.find(g, 0.65)
   print (g1)

以下是手势识别练习的完整代码 -

from kivy.app import App
from kivy.graphics import *
from kivy.uix.floatlayout import FloatLayout
from kivy.gesture import Gesture, GestureDatabase
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from random import random
from kivy.core.window import Window

Window.size = (720, 400)

class Drawgesture(Widget):
   points = []
   
   def __init__(self, *args, **kwargs):
      super(Drawgesture, self).__init__()
      self.gdb = GestureDatabase()

   def on_touch_down(self, touch):
      with self.canvas:
         self.col = (random(), random(), random())
         Color(self.col)
         touch.ud["line"] = Line(points=(touch.x, touch.y), width=3)
         Drawgesture.points.append((touch.x, touch.y))

   def on_touch_move(self, touch):
      with self.canvas:
         Color(self.col)
         touch.ud["line"].points += (touch.x, touch.y)
         Drawgesture.points.append((touch.x, touch.y))
         
   def on_touch_up(self, touch):
      print('touch up')

class gestureApp(App):
   def pressed(self, instance):
      if instance.text == 'Add':
         g = Gesture()
         g.add_stroke(point_list=Drawgesture.points)
         g.normalize()
         print(self.d.gdb.gesture_to_str(g))
         self.d.gdb.add_gesture(g)
         print(str(g))
         
      if instance.text == 'Find':
         g = Gesture()
         g.add_stroke(point_list=Drawgesture.points)
         g.normalize()
         g1 = self.d.gdb.find(g, 0.65)
         print(g1)

      if instance.text == 'Clear':
         self.d.canvas.clear()
         
   def build(self):
      f = FloatLayout()
      self.d = Drawgesture()
      f.add_widget(self.d)
      b1 = Button(
         text='Add', pos_hint={'x': 0, 'y': 0},
         size_hint=(None, None)
      )
      f.add_widget(b1)
      b2 = Button(
         text='Find', pos_hint={'x': .2, 'y': 0}, 
         size_hint=(None, None)
      )
      f.add_widget(b2)
      b3 = Button(
         text='Clear', pos_hint={'x': .4, 'y': 0},
         size_hint=(None, None)
      )
      f.add_widget(b3)
      b1.bind(on_press=self.pressed)
      b2.bind(on_press=self.pressed)
      b3.bind(on_press=self.pressed)
      return f
      
gestureApp().run()

输出

如果匹配分数大于或等于 min_score 参数,则您将获得以下结果 -

(0.7093289348205829, <kivy.gesture.Gesture object at 0x000001B817C70490>)

Kivy - 语言

本章解释了 Kivy 设计语言的重要特性、语法和用法。Kivy 语言(也称为“kv”语言)主要描述 Kivy 应用程序的用户界面。它类似于 Qt 的 QML。 “kv”语言中的规则定义类似于 CSS 规则。

目的

Kivy 应用程序中窗口小部件的布局和放置被编码到 Kivy 的 App 类的 build() 方法中。对于用户界面更简单的应用程序,这可能效果很好。但是,随着布局设计的复杂性增加,它很快就会变得过于冗长且难以维护。因此,需要将应用程序的设计部分与处理逻辑分离。“kv”语言有助于实现这种关注点分离。

使用“kv”语言脚本,您可以以声明方式创建窗口小部件树,并以更自然的方式将窗口小部件属性绑定到回调。以编程方式设计界面的缺点之一是,在应用程序执行之前无法查看外观。另一方面,kviewer 实用程序提供了设计和窗口小部件放置的交互式视图,而无需在应用程序中加载脚本。

加载“kv”脚本

一旦窗口小部件的设计完成,它就会加载到 App 类中。这可以通过两种方式完成。

使用命名约定

将“kv”脚本保存为“.kv”扩展名,并将其名称与 App 类的名称对应。如果 App 类名称以 app 关键字结尾,则必须省略它。例如,如果 App 类命名为 DemoApp,如下所示,

Class DemoApp(App):
   ...
   ...

则 kv 文件应命名为 demo.kv。Kivy 会自动将设计加载到 App 类的 build() 方法中。如果 kv 文件定义了一个根窗口小部件,它将附加到 App 的 root 属性,并用作应用程序窗口小部件树的基础。

使用 Builder 类

如果“kv”文件的名称不遵循上述约定,则可以使用“kivy.lang”模块中提供的 Loader 类的 load_file() 方法将脚本加载到应用程序中。

from kivy.app import App
from kivy.lang import Builder

Builder.load_file('mykv.kv')

MydemoApp(App):
   def build(self):
      pass
      
MydemoApp().run()

您还可以将整个“kv”语言脚本作为字符串嵌入到 Python 代码中,并使用 Builder.load_string() 方法在 App 类中加载它。

from kivy.app import App
from kivy.lang import Builder

kv="""
#kv script goes here
"""

MydemoApp(App):
   def build(self):
      Builder.load_string(kv)
      
MydemoApp().run()

“kv”语言具有以下重要元素 -

  • 规则 - “kv”中的规则类似于 CSS 规则。它适用于特定窗口小部件或继承指定窗口小部件类的类。“kv”规则可以指定交互行为或用于添加它们所应用的窗口小部件的图形表示。

  • 根窗口小部件 - 您可以使用该语言创建整个用户界面。“kv”文件最多只能包含一个根窗口小部件。

  • 动态类 - 动态类允许您即时创建新的窗口小部件和规则,无需任何 Python 声明。

kv 脚本描述了窗口小部件的内容。您可以拥有一个根规则和任意数量的类或模板规则。根规则通过声明根窗口小部件的类来声明,不缩进,后跟“:”符号。它设置 App 实例的 root 属性。

Widget:

要声明类规则,请将窗口小部件类的名称放在“< >”之间,后跟“:”符号。它定义了该类任何实例的外观和行为 -

<MyWidget>:

Kv 语言规则使用缩进进行分隔,与 Python 源代码相同。要记住的经验法则是,带有尖括号的是规则,没有尖括号的是根窗口小部件。

示意图示例 -

<MyClass>:
   prop1: value1
   prop2: value2
   
   canvas:
      CanvasInstruction1:
         canvasprop1: value1
      CanvasInstruction2:
         canvasprop2: value2
   AnotherClass:
      prop3: value1

有三个特定于“kv”语言的关键字 -

self - 始终指当前窗口小部件。

Button:
text: 'My state is %s' % self.state

root - 指当前规则中的基窗口小部件,并表示规则的根窗口小部件(规则的第一个实例) -

<MyWidget>:
   custom: 'Hello world'
   Button:
      text: root.custom

app - 始终指应用程序的实例。

Label:
   text: app.name

Kivy 应用程序窗口只能包含一个小部件作为其根对象。但是,如果您需要使用多个控件来组合应用程序界面,则必须使用布局小部件,并将多个 UX 小部件放置在其中,然后将布局作为根小部件放在应用程序窗口上。

以下“kv”文件脚本定义了一个网格布局,然后可以将其与 App 类一起使用 -

GridLayout:
   cols:1
   Label:
      text:'Hello'
      font_size:100
      color:(1,0,0,1)
   Button:
      text='Ok'
      font_size:100

在上述情况下,Label 窗口小部件和 Button 位于一个列的 GridLayout 中。但是,无法从 Python 代码中访问对象及其属性。要能够这样做,请在“kv”脚本中定义 id 属性。

GridLayout:
   cols:1
   Label:
      id:l1
      text:'Hello'
      font_size:60
      color:(1,0,0,1)
   Button:
   id:b1
      text='Ok'
      font_size:60

当此布局加载到 build() 方法中时,App 类可以通过此 id 访问窗口小部件属性。它有一个 ids 属性,它是一个包含 id:value 键值对的字典。

print (self.ids.l1.text)

让我们从以下 kv 脚本开始,它包含网格布局中的标签和按钮。

GridLayout:
   cols:1
   Label:
      text:'Hello'
      font_size:60
      color:(1,0,0,1)
   TextInput:
      text:'Hello'
      font_size:60
   Button:
      text:'Ok'
         font_size:60

Kivy 应用程序代码加载上述“kvdemo.kv”脚本 -

from kivy.app import App
from kivy.core.window import Window

Window.size = (720,400)
class KvDemoApp(App):
   def build(self):
      pass
      
KvDemoApp().run()

让我们在 Kivy 应用程序中添加一个事件处理程序。要访问窗口小部件属性,我们为标签、文本输入和按钮定义 id 属性。我们在“kv”脚本中定义了一个类规则,并在顶部用尖括号括住 MyWidget。

<MyWidget>:
   cols:1
   size:root.size
   Label:
      id:l1
      text:'Hello'
      font_size:60
      color:(1,0,0,1)
   TextInput:
      id:t1
      text:'Hello'
      font_size:60
   Button:
      id:b1
      text:'Ok'
      font_size:60
      on_press:root.callback(*args)

请注意,按钮的 on_press 属性绑定到我们将在根类(即 MyWidget 类)中编写的 callback() 方法。

MyWidget 类继承 GridLayout 窗口小部件。在类内部,提供了一个 callback() 方法。当按下按钮时,它会将标签标题更新为文本框中输入的文本。

在“kvdemoapp.py”代码中添加以下类 -

from kivy.uix.gridlayout import GridLayout
class MyWidget(GridLayout):
def callback(self, *args):
print ("hello")
self.ids.l1.text=self.ids.t1.text

运行程序后,在文本框中输入一些文本,然后按下按钮。标签上的文本将相应更改。

Kivy Language

我们也可以仅在“kv”脚本中提供整个逻辑。我们不需要在 Python 类中定义回调。从“kv”代码中使用标签和文本输入的 id 属性。

<MyWidget>:
   cols:1
   size:root.size
   Label:
      id:l1
      text:'Hello'
      font_size:60
      color:(1,0,0,1)
   TextInput:
      id:t1
      text:'Hello'
      font_size:60
   Button:
      id:b1
      text:'Ok'
      font_size:60
      on_press: l1.text=t1.text

在这里,on_press 事件将标签(id 为 l1)的文本设置为 id 为“t1”的文本框中的文本。在 Python 代码端,MyWidget 类将只有一个 pass 语句。

因此,我们可以看到,使用“kv”设计语言使应用程序开发变得非常方便,因为编程部分和设计部分是隔离的。

Kivy - 图形

Kivy 框架配备了强大的图形功能,这些功能构建在 OpenGL 和 SDL 指令之上。Kivy 与许多图形工具包一样,提供了一个画布来渲染图形。但是,Kivy 的画布与在其他框架中找到的 Canvas 对象不同,例如 HTML5 中的 Canvas,甚至 Python 的 TKinter 库中的 Canvas。让我们尝试了解在 Kivy 中使用图形的不同之处。

在 Kivy 中,图形画布是一组绘图指令,用于定义窗口小部件的图形表示,您可以将其视为无限的绘图板。尽管 Kivy 库中的每个 GUI 窗口小部件都有一个 Canvas,但所有 Kivy 窗口小部件共享相同的坐标空间,您可以在其中绘制。坐标空间不受窗口或应用程序窗口大小的限制,因此我们甚至可以在可见区域之外绘制。

Kivy 中有两种类型的图形指令 -

  • 顶点指令 - 用于绘制基本几何形状(如线条、矩形、椭圆等)的指令称为顶点指令。

  • 上下文指令 - 这些指令不绘制任何内容,而是操纵整个坐标空间,以便为其添加颜色、旋转、平移和缩放它。

某个上下文指令对画布对象的影响 - 例如,如果我们将旋转指令应用于按钮的画布 - 将在所有后续图形指令中可见,因为所有窗口小部件都共享坐标空间。

顶点指令

OpenGL 使用顶点来描述其图形对象。顶点指令是绘制图形的指令。常见的顶点指令包括 Point、Line、Triangle、Rectangle、Ellipse、Mesh、Bezier 等。Kivy - 绘制章节详细解释了绘图指令的使用。

上下文指令

Kivy 的图形指令包括 Rotate、Translate 和 Scale 指令。它们是应用于顶点指令的上下文指令,这些指令显示在坐标空间中

旋转

Rotate 指令在对象的画布上下文中工作。对象以给定角度和沿给定轴旋转。

with self.canvas:
   rotate = Rotate(angle=45)

旋转是根据传递给它的参数执行的。

  • angle - 获取/设置旋转角度的属性。范围在 0 到 360 之间

  • axis - 获取/设置旋转轴的属性。轴的格式为 (x, y, z)。

  • origin - 旋转的原点。原点的格式可以是 (x, y) 或 (x, y, z)。

  • set(float angle, float ax, float ay, float az) - 此函数设置旋转的角度和轴。

缩放

Scale 指令在对象的画布上下文中工作。它根据缩放比例更改对象沿给定轴的大小。

with self.canvas:
   s=Scale(2,2,1)

Scale 指令可以使用一个或三个参数创建。

Scale(x, y, z)

Scale 指令接受以下参数:

origin - 缩放的原点。原点的格式可以是 (x, y) 或 (x, y, z)。

  • x - 获取/设置 X 轴缩放比例的属性。

  • y - 获取/设置 Y 轴缩放比例的属性。

  • z - 获取/设置 Z 轴缩放比例的属性。

  • xyz - 3D 中 x、y 和 z 轴上的 3 元组缩放向量。

特定对象画布下的缩放和旋转指令会导致整个画布发生变化,因为画布共享相同的坐标空间。因此,为了检索其他部件的位置和状态,有 PushMatrix 和 PopMatrix 指令。

  • PushMatrix 保存当前坐标空间上下文。

  • PopMatrix 检索最后保存的坐标空间上下文。

因此,由 PushMatrix 和 PopMatrix 包围的变换指令(Scale、Rotate 和 Translate)不会影响界面的其余部分。

示例

我们首先在应用窗口上放置一个标签和两个按钮。我们使用 FloatLayout,它使我们能够自由地在特定坐标处放置部件并指定其大小。

通过在标签的画布上绘制一个彩色矩形,为标签提供背景颜色。

self.lbl = Label(
   text= 'Hello World', font_size=24,
   halign='center',size_hint=(.2, .1),
   pos=(300, 250)
)
with self.lbl.canvas:
   Color(0, 1, 0, 0.25)
   Rectangle(pos=self.lbl.pos, size=(200,50))

按钮标题“Rotate”绑定到 update_rotate() 方法,该方法应用 45 度旋转。请注意,PushMatrix 在旋转前存储坐标空间,并在旋转后调用 PopMatrix。

   def update_rotate(self, instance):
      with self.lbl.canvas.before:
         PushMatrix()
         rotate = Rotate(angle=45)
      with self.lbl.canvas.after:
         PopMatrix()
         rotate.origin = self.lbl.center

当按下“Scale”按钮时,会导致“Rotate”按钮沿 X 和 Y 轴缩放。为此目的的 update_scale() 方法如下所示:

def update_scale(self, instance):
   with self.btn.canvas.before:
      PushMatrix()
      s = Scale(2, 2, 1)

   with self.btn.canvas.after:
      PopMatrix()
      s.origin = self.btn.center

完整的示例代码如下所示:

from kivy.app import App
from kivy.graphics import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.core.window import Window

Window.size = (720, 400)

class RotationApp(App):
   def update_rotate(self, instance):
      with self.lbl.canvas.before:
         PushMatrix()
         rotate = Rotate(angle=45)
      with self.lbl.canvas.after:
         PopMatrix()
         rotate.origin = self.lbl.center
   
   def update_scale(self, instance):
      with self.btn.canvas.before:
         PushMatrix()
         s = Scale(2, 2, 1)
      with self.btn.canvas.after:
         PopMatrix()
         s.origin = self.btn.center
   
   def build(self):
      root = FloatLayout()
      self.lbl = Label(
         text='Hello World', font_size=24,
         halign='center', size_hint=(.2, .1),
         pos=(300, 250)
      )
      with self.lbl.canvas:
         Color(0, 1, 0, 0.25)
         Rectangle(pos=self.lbl.pos, size=(200, 50))
      self.btn = Button(
         text='Rotate', size_hint=(None, None),
         pos_hint={'center_x': .3, 'center_y': .1}
      )
      root.add_widget(self.lbl)
      self.btn1 = Button(
         text='Scale', size_hint=(None, None),
         pos_hint={'center_x': .6, 'center_y': .1}
      )
      self.btn.bind(on_press=self.update_rotate)
      self.btn1.bind(on_press=self.update_scale)
      root.add_widget(self.btn)
      root.add_widget(self.btn1)
      return root
      
RotationApp().run()

输出

运行以上代码。当您点击“Rotate”按钮时,“Hello World”标签将旋转 45 度。

Kivy Graphics

点击“Scale”按钮。它将更改“Rotate”按钮沿 X 和 Y 轴的尺寸。

Kivy Button Rotate

Kivy - 绘制

Kivy 库中的所有 GUI 部件都具有 Canvas 属性。Canvas 是用于绘制各种对象(例如矩形、椭圆等)的地方。需要注意的是,Kivy 没有单独的 Canvas 部件用于绘制形状。所有部件的画布共享一个公共的坐标空间。

在 Kivy 中,绘制是在与任何部件相关的 Canvas 上完成的,使用顶点指令和上下文指令。

  • 顶点指令 - 用于绘制基本几何形状(如线条、矩形、椭圆等)的指令称为顶点指令。

  • 上下文指令 - 这些指令不绘制任何内容,而是操纵整个坐标空间,以便为其添加颜色、旋转、平移和缩放它。

顶点指令

这些指令以不同顶点对象的格式添加到 Canvas 中。顶点类在 kivy.graphics.vertex_instructions 模块中定义。如上所述,绘图指令添加到 Canvas 的上下文中。

with self.canvas:
   vertex(**args)

vertex_instructions 模块包含以下类:

  • Bezier

  • BorderImage

  • Ellipse

  • Line

  • Mesh

  • Point

  • Quad

  • Rectangle

  • RoundedRectangle

  • SmoothLine

  • Triangle

Bezier

贝塞尔曲线由一些控制点加权,这些控制点包含在指令中。在 Kivy 中,Bezier 是一种顶点画布指令,它根据作为参数提供给 Beizer 构造函数的一组点绘制此曲线。

from kivy.graphics import Beizer
with self.canvas:
   Beizer(**args)

参数

Bezier 类中定义了以下参数:

  • points - 点列表,格式为 (x1, y1, x2, y2…)

  • loop - bool,默认为 False,将贝塞尔曲线设置为连接最后一个点到第一个点。

  • segments - int,默认为 180。定义绘制曲线所需的段数。更多的段数会导致更平滑的绘制。

  • dash_length - 段的长度(如果虚线),默认为 1。

  • dash_offset - 一段的末端与下一段的起始之间的距离,默认为 0。更改此值会使其成为虚线。

示例

self.w=Widget()
with self.w.canvas:
   Color(0,1,1,1)
   Bezier(
      points=[700,400,450,300,300,350,350, 200,200,100,150,10],
      segments=20
   )
   Color(1,1,0,1)
   Point(
      points =[700,400,450,300,300,350,350, 200,200,100,150,10],
      pointsize= 3
   )

输出

它将生成以下输出窗口:

kivy drawing bezier

此处显示的点仅供参考。

Ellipse

在 Kivy 框架中,Ellipse 是一个顶点指令。根据所需的段数,它可以显示多边形、矩形或弧形。如果宽度和高度参数相等,则结果为圆形。

from kivy.graphics import Ellipse
with self.canvas:
   Ellipse(**args)

参数

Ellipse 类中定义了以下参数:

  • pos - 给出椭圆中心 X 和 Y 坐标值的两个元素元组。

  • size - 定义椭圆宽度和高度(以像素为单位)的两个元素元组。

  • angle_start - float,默认为 0.0,指定起始角度(以度为单位)。

  • angle_end - float,默认为 360.0,指定结束角度(以度为单位)。

  • segments - 椭圆的段数。如果您有许多段,则椭圆绘制将更平滑。使用此属性创建具有 3 个或更多边的多边形。小于 3 的值将不会显示。

示例

self.w=Widget()
with self.w.canvas:
   Color(0.5, .2, 0.4, 1)
   d = 250
   Ellipse(pos=(360,200), size=(d+75, d))

输出

它将生成以下输出窗口:

Kivy Drawing Ellipse

Rectangle

此顶点指令根据作为参数给定的位置和尺寸在画布上绘制一个矩形。

from kivy.graphics import Rectangle
with self.canvas:
   Rectangle(**args)

参数

Rectangle 类中定义了以下参数:

  • pos - 整数列表,指定矩形的位置,格式为 (x, y)。

  • size - 整数列表,指定矩形的大小,格式为 (width, height)。

绘制填充有特定颜色的矩形是为标签提供背景的推荐方法。

示例

def build(self):
   widget = Widget()
   with widget.canvas:
   Color(0, 0, 1, 1)
      Rectangle(
      pos=(50, 300), size_hint=(None, None),
      size=(300, 200)
   )
   lbl = Label(
      text='Hello World', font_size=24,
      pos=(Window.width / 2, 300), size=(200, 200),
      color=(0, 0, 1, 1)
   )
   with lbl.canvas.before:
      Color(1, 1, 0)
      Rectangle(pos=lbl.pos, size=lbl.size)
   
   widget.add_widget(lbl)
   
   return widget

输出

它将生成以下输出窗口:

Kivy Drawing Rectangle

需要注意的是,Quad 是一个四边形,是一个具有四个顶点的多边形,不一定是矩形。类似地,圆角矩形是一个具有圆角顶点的矩形。

Line

在 Kivy 图形中,Line 是一个基本的顶点指令。Line 对象构造函数的 points 属性具有连续点的 x 和 y 坐标。Kivy 绘制一条依次连接这些点的线。

from kivy.graphics import Line
with self.canvas:
   Line(**args)

参数

Line 类中定义了以下参数:

  • points - 点列表,格式为 (x1, y1, x2, y2…)

  • dash_length - int,段的长度(如果虚线),默认为 1。

  • dash_offset - 一段的末端与下一段的起始之间的偏移量,默认为 0。更改此值会使其成为虚线。

  • dashes - 格式为 [ON 长度,偏移量,ON 长度,偏移量,…] 的整数列表。例如 [2,4,1,6,8,2] 将创建一个线条,第一个虚线长度为 2,然后偏移量为 4,然后虚线长度为 1,然后偏移量为 6,依此类推。

  • width - float - 定义线条的宽度,默认为 1.0。

示例

def build(self):
   box = BoxLayout(orientation='vertical')
   self.w = Widget()
   with self.w.canvas:
      Color(1, 0, 0, 1)
      Line(
         points=[700, 400, 450, 300, 300, 350, 350, 200, 200,  100, 150, 10],
         width=4
      )
      box.add_widget(self.w)
      
   return box

输出

它将生成以下输出窗口:

Kivy Drawing Line

Line 类还具有 bezier、ellipse、circle、rectangle 属性,线条点用于绘制相应的形状。

Triangle

使用此顶点指令,Kivy 使用 points 列表绘制一个三角形。

from kivy.graphics import Triangle
with self.canvas:
   Triangle(**args)

points 属性是三角形三个顶点的 x 和 y 坐标列表,格式为 (x1, y1, x2, y2, x3, y3)。

示例

self.w=Widget()
with self.w.canvas:
   Color(1,1,1,1)
   self.triangle = Triangle(points=[100,100, 300,300, 500,100])

输出

它将生成以下输出窗口:

Kivy Drawing Triangle

更新绘图

需要注意的是,所有图形指令类的列表属性(例如 Triangle.points、Mesh.indices 等)不是 Kivy 属性,而是 Python 属性。因此,修改此列表不会更新绘图。只有当列表对象本身发生更改时才会更新,而不是当列表值被修改时。

让我们通过将点 (300,300) 更改为 (400,300) 来更改上述矩形的顶点。我们在上述布局中添加一个按钮,并将其绑定到一个更新方法。

示例

以下是完整代码:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.widget import Widget
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics import *
from kivy.properties import ObjectProperty
from kivy.core.window import Window

Window.size = (720, 400)

class mytriangleapp(App):
   triangle = ObjectProperty(None)
   title = "Drawing app"

   def updating(self, *args):
      self.triangle.points[3] = 400
      self.triangle.points = self.triangle.points

   def build(self):
      box = BoxLayout(orientation='vertical')
      self.w = Widget()
      with self.w.canvas:
         Color(1, 1, 1, 1)
         self.triangle = Triangle(points=[100, 100, 300, 300, 500, 100])
      self.b1 = Button(
         text='update', on_press=self.updating,
         size_hint=(None, .1)
      )
      box.add_widget(self.w)
      box.add_widget(self.b1)
      return box
mytriangleapp().run()

输出

执行上述程序时,我们会看到一个三角形和一个按钮。当点击按钮时,“triangle.points”列表会更新,从而更新三角形的绘制。

Kivy Drawing Updating

Kivy - 打包

术语“打包”是指创建应用程序源代码的单个包,以及所有依赖项,包括库、数据文件配置文件等。

当您开发 Kivy 应用时,它需要各种资源。例如,常见的要求是 sdl2 包或 glew 包。当您安装 Kivy 时,这些依赖项也会被安装。

kivy-deps.glew
kivy-deps.gstreamer
kivy-deps.sdl2

到目前为止,您一直在从已安装 Python 运行时的机器上运行 Kivy 应用。但是,当需要将此应用移植到另一台未安装 Python 的机器时,您需要构建一个包含程序以及 Python 运行时和依赖项的包。

PyInstaller 包可帮助您构建应用的可重新分发包。用户无需安装 Python、Kivy 或任何其他库即可运行应用。

要构建这样的可重新分发包,您应该首先使用 PIP 命令在当前的 Kivy 环境中安装 PyInstaller。

pip3 install -U pyinstaller

下一步是将一个或多个 Python 源文件(扩展名为 .py)以及其他资源(例如图像文件等)收集到一个单独的文件夹中。

在本练习中,我们将为 ImageButton 应用构建一个包。此应用的文件存储在 imgbtn 文件夹中。

Directory of C:\kivyenv\imgbtn
forward.png main.py pause.png play.png
previous.png

创建另一个文件夹 ImangBtnApp,该文件夹最终将存储可重新分发包。在文件夹内,执行以下命令:

(kivyenv) C:\kivyenv\ImageBtnApp>pyinstaller -n ImageBtnApp
c:\kivyenv\imgbtn\main.py

Kivy 应用有很多依赖项。因此,收集所有依赖项可能需要一段时间。最终,ImageButtonApp 文件夹将填充以下内容:

Directory of C:\kivyenv\ImageBtnApp

27-07-2023 21:25    <DIR>           .
27-07-2023 21:07    <DIR>           ..
27-07-2023 21:25    <DIR>           build
27-07-2023 21:28    <DIR>           dist
27-07-2023 21:25                    970 ImageBtnApp.spec

dist 文件夹是可重新分发文件夹,其中您将找到 EXE 文件“ImageBtnApp.exe”,以及所需的 DLL 库(例如 sdl2 等)。

有一个与应用名称相同的 spec 文件。我们需要编辑 spec 文件以添加依赖项钩子以正确构建 exe。

使用您喜欢的编辑器打开 spec 文件,并在 spec 的开头添加以下行:

from kivy_deps import sdl2, glew

向下滚动 spec 文件以找到 COLLECT 部分,并为依赖项的每个路径添加一个 Tree 对象。例如,*[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)]。

coll = COLLECT(
   exe, Tree('c:\\kivyenv\\imgbtn\\'),
   a.binaries,
   a.zipfiles,
   a.datas,
   *[Tree(p) for p in (sdl2.dep_bins + glew.dep_bins)],
   strip=False,
   upx=True,
   upx_exclude=[],
   name='ImageBtnApp',
)

现在,我们使用以下命令在 ImageBtnApp 中构建 spec 文件:

python -m PyInstaller ImageBtnApp.spec

编译后的包将位于 ImageBtnApp\dist\ImageBtnApp 目录中。

您还可以将运行时、应用程序代码和依赖项放在一个文件中(而不是可重新分发包中),方法是使用 Pyinstaller 命令行语法的 -onetime 开关:

pyinstaller --onefile -n ImageBtnApp

要构建针对 Android 的可重新分发包,您需要使用 Buildozer 工具。它会下载并设置 python-for-android 的所有先决条件,包括 android SDK 和 NDK,然后构建一个可以自动推送到设备的 apk。

导航到您的项目目录并运行:

buildozer init

这将创建一个控制构建配置的 buildozer.spec 文件。使用您的应用名称等编辑该文件。您可以设置变量来控制传递给 python-for-android 的大部分或所有参数。最后,插入您的 Android 设备并运行:

buildozer android debug deploy run

以构建、推送并在您的设备上自动运行 APK。

Kivy - Garden

Kivy Garden 是个人用户开发的 Kivy 部件的存储库。这是一个由用户维护的项目,其目标是集中 Kivy 的附加组件。用户贡献的 Kivy 包托管在 Kivy Garden 存储库 https://github.com/kivy-garden 上。

用户开发并上传到 Garden 存储库的部件称为 Flowers。Kivy Garden 中的花有两种类型。在 Kivy 1.11.0 版本之前的花是旧版花。要安装旧版花部件,您需要使用以下命令:

garden install flower-name

旧版花不是正确的 Python 包,并且名称以 garden 前缀附加。例如,用作 Kivy 的 Matplotlib 后端的部件是 garden.matplotlib。

另一方面,新花是 Python 包,它们托管在 PyPI 存储库上,因此可以使用常规的 pip 实用程序安装。

pip install flower

现代的 Kivy 花(扩展)没有 garden 前缀。例如,mapview 控件提供了一个容器,用于在 Kivy 应用中显示交互式地图。

pip install mapview

您可以直接从 github 安装 master 分支。例如,以下命令安装 graph 花(扩展):

python -m pip install

https://github.com/kivy-garden/graph/archive/master.zip

示例

让我们在 Kivy 应用中使用 mapview 花(扩展):

from kivy_garden.mapview import MapView
from kivy.app import App
from kivy.core.window import Window
Window.size = (720,400)

class MapViewApp(App):
   def build(self):
      mapview = MapView(zoom=11, lat=50.6394, lon=3.057)
      return mapview
      
MapViewApp().run()

输出

运行此代码时,它将生成以下输出窗口:

Kivy Garden

Kivy - 存储

Kivy 框架中的 Storage 类用于通过索引条目加载和存储任意数量的键值对。 "kivy.storage" 模块定义了 AbstractStore 类。其实现 - **DictStore**、**JsonStore** 和 **RedisStore** - 提供了具体的类。

  • kivy.storage.dictstore.DictStore:使用 Python 字典作为存储。

  • kivy.storage.jsonstore.JsonStore:使用 JSON 文件作为存储。

  • kivy.storage.redisstore.RedisStore:使用 Redis 数据库和 redis-py 库。

要使用上述任何存储类,请导入相关的类,声明一个对象并调用其 put() 方法来存储键值对。对于 JsonStore:

from kivy.storage.jsonstore import JsonStore

store = JsonStore('hello.json')

# put some values
store.put(name, key1=val1, key2=val2)

这将在当前目录中创建 hello.json 文件。您可以使用 get() 方法检索信息。

print (store.get(name)[key])

AbstractStore 类中定义了以下方法,这些方法需要由具体的实现(如 DictStore)覆盖:

  • **clear()** - 清空整个存储。

  • **count()** - 返回存储中条目的数量。

  • **delete(key)** - 从存储中删除一个键。如果未找到该键,则会抛出 KeyError 异常。

  • **exists(key)** - 检查存储中是否存在某个键。

  • **find(**filters)** - 返回与过滤器匹配的所有条目。条目通过生成器作为 (key, entry) 对列表返回,其中 entry 是键值对的字典。

  • **get(key)** - 获取存储在键处的键值对。如果未找到该键,则会抛出 KeyError 异常。

  • **keys()** - 返回存储中所有键的列表。

  • **put(key, **values)** - 将新的键值对(在 values 中给出)放入存储中。任何现有的键值对都将被删除。

这些方法 (get()、put()、exists()、delete()、find()) 都有异步版本。这些方法可以带或不带回调参数调用。如果给出回调参数,则请求将是异步的,并在结果可用时将其返回给用户。如果回调参数为 None,则请求将是同步的,结果将直接返回。

示例

以下是一个示例:

# synchronous
res=store.get(key)
print (res)

# asynchronous
def my_callback(store, key, result):
   print (result)
   
store.get(key) 

回调函数应具有以下参数:

  • **store** - 当前使用的 "Store" 实例。

  • **key** - 搜索的键。

  • **result** - 对键进行查找的结果。

示例

from kivy.storage.jsonstore import JsonStore
from kivy.storage.dictstore import DictStore

store = JsonStore('store.json')

# put some values
store.put('state', name='Maharashtra', capital='Mumbai',
population='Eleven Cr')
store.put('os', name='Windows', version=11, released=2021)
store.put('shape', type='circle', radius=5)

# using the same index key erases all previously added k-v pairs
# get a value using a index key and key
print('Population of ', store.get('state')['name'], 'is ',
store.get('state')['population'])
print (store.get('state').keys())
for k,v in store.get('state').items():
   print (k,":",v)
   
# or guess the key/entry for a part of the key
for item in store.find(type='circle'):
   print('Store:',item[0])
   print('K-V pairs: ',str(item[1]))

输出

它将产生以下输出 -

Population of Maharashtra is Eleven Cr
dict_keys(['name', 'capital', 'population'])
name : Maharashtra
capital : Mumbai
population : Eleven Cr
Store: shape
K-V pairs: {'type': 'circle', 'radius': 5}

Kivy - 向量

在欧几里得几何中,向量是一个表示具有大小和方向的物理量的对象。Kivy 库包含 Vector 类,并提供执行二维向量运算的功能。

Vector 类定义在 kivy.vector 模块中。Kivy 的 Vector 类继承了 Python 的内置列表类。通过在笛卡尔坐标系中传递 x 和 y 坐标值来实例化 Vector 对象。

from kivy.vector import Vector
v=vector(10,10)

这两个参数可以通过下标运算符访问。第一个参数是 v[0],第二个参数是 v[1]。

print (v[0], v[1])

它们也被识别为 Vector 对象的 x 和 y 属性。

print (v.x, v.y)

您还可以通过将两个值的列表或元组传递给构造函数来初始化向量。

vals = [10,10]
v = Vector(vals)

示例

Kivy 中的 Vector 类支持由常见的算术运算符 +、-、/ 表示的向量运算。

两个向量 (a,b)+(c,d) 的加法结果为向量 (a+c, b+d)。类似地,"(a,b) - (c,d)" 等于 "(a - c, b - d)"。

from kivy.vector import Vector

a = (10, 10)
b = (87, 34)
print ("addition:",Vector(1, 1) + Vector(9, 5))
print ("Subtraction:",Vector(9, 5) - Vector(5, 5))
print ("Division:",Vector(10, 10) / Vector(2., 4.))
print ("division:",Vector(10, 10) / 5.)

输出

addition: [10, 6]
Subtraction: [4, 0]
Division: [5.0, 2.5]
division: [2.0, 2.0]

Vector 类中的方法

以下方法定义在 Kivy 的 Vector 类中:

angle()

它计算向量与参数向量之间的角度,并以度为单位返回角度。

在数学上,向量之间的角度由以下公式计算:

$$\theta =cos^{-1}\left [ \frac{x\cdot y}{\left| x\right|\left|y \right|} \right ]$$

查找角度的 Kivy 代码如下:

示例

a=Vector(100, 0)
b=(0, 100)
print ("angle:",a.angle(b))

输出

angle: -90.0

distance()

它返回两点之间的距离。两个向量之间的欧几里得距离由以下公式计算:

$$d\left ( p,q \right )=\sqrt{\left ( q_{1}-p_{1} \right )^{2}+\left ( q_{2}-p_{2} \right )^{2}}$$

distance() 方法更易于使用。

示例

a = Vector(90, 33)
b = Vector(76, 34)
print ("Distance:",a.distance(b))

输出

Distance: 14.035668847618199

distance2()

它返回两点之间的距离的平方。两个向量 x = [ x1, x2 ] 和 y = [ y1, y2 ] 之间的平方距离是其坐标的平方差之和。

示例

a = (10, 10)
b = (5,10)
print ("Squared distance:",Vector(a).distance2(b))

输出

Squared distance: 25

dot(a)

计算 "a" 和 "b" 的点积。点积(也称为标量积)是向量 b 的大小乘以 "a" 在 "b" 上的投影的大小。投影的大小为 $cos\theta$(其中 $\theta$ 是两个向量之间的角度)。

示例

print ("dot product:",Vector(2, 4).dot((2, 2)))

输出

dot product: 12

length()

它返回向量的长度。length2() 方法返回向量的长度的平方。

示例

pos = (10, 10)
print ("length:",Vector(pos).length())
print ("length2:",Vector(pos).length2())

输出

length: 14.142135623730951
length2: 200

rotate(angle)

以度为单位旋转向量。

示例

v = Vector(100, 0)
print ("rotate:",v.rotate(45))

输出

rotate: [70.71067811865476, 70.71067811865476]

Kivy - 工具

Kivy 库中的 "kivy.utils" 模块是各种类别(如数学、颜色、代数函数等)中通用实用函数的集合。

QueryDict

QueryDict 类的对象类似于 Python 的内置 dict 类。此外,它还提供了一个使用点 (.) 运算符查询对象的机制。

要构造 QueryDict,您可以传递一个由两个元素组成的元组列表,或者传递一个字典对象本身。

# list of tuples
qd = QueryDict([('a',1), ('b',2)])
print (qd)

列表的每个元组元素都应该有两个项目。第一个项目是键,第二个项目是其值。

{'a': 1, 'b': 2}

另一方面,您可以将字典对象本身传递给 QueryDict 构造函数。

qd=QueryDict({'a':1, 'b':2})

虽然可以使用标准 dict 中定义的 [] 运算符获取属于某个键的值,但 QueryDict 提供了一个点运算符。因此,"qd.k" 与 "qd['k']" 相同。请注意,dict 类的 get() 方法也可以与 QueryDict 一起使用。

您可以使用传统的切片运算符赋值或点运算符更新键的值。

qd.a=100
qd['b']=200

请尝试以下 **示例**:

from kivy.utils import *

# list of tuples
qd=QueryDict([('a',1), ('b',2)])
print (qd)
print (qd.a, qd['a'])

qd=QueryDict({'a':1, 'b':2})
print (qd)
print (qd.b, qd['b'])
print (qd.get('a'))

qd.a=100
qd['b']=200
print (qd)

SafeList

Kivy 中的 SafeList 类继承了内置的 list 类。除了从 list 继承的方法之外,SafeList 类中还定义了一个新方法 - clear()。它删除列表中的所有项目。

您可以将可变序列(列表)传递给构造函数以创建 SafeList 对象。如果未传递参数,则它将创建一个空列表。调用 clear() 方法将删除所有项目。

示例

from kivy.utils import *

sl = SafeList([1,2,3,4])
print ("SafeList:",sl)

l = [1,2,3,4]
sl = SafeList(l)
print ("SafeList:",sl)

sl.clear()
print ("SafeList:",sl)

输出

SafeList: [1, 2, 3, 4]
SafeList: [1, 2, 3, 4]
SafeList: []

difference()

此函数返回两个列表之间的差集。更具体地说,它从第一个列表中删除在第二个列表中找到的那些项目。

示例

from kivy.utils import *

l1=[1,2,3,4]
l2=[3,4,5,6]
print (l1, l2)
print ("l1-l2:",difference(l1,l2))
print ("l2-l1:",difference(l2,l1))

输出

[1, 2, 3, 4] [3, 4, 5, 6]
l1-l2: [1, 2]
l2-l1: [5, 6]

escape_markup()

Kivy 应用窗口上的 Label 能够显示标记文本。但是,如果您希望标记符号的效果不生效,则可以转义文本中找到的标记字符。这旨在用于在 Label 上激活标记文本时使用。

在下面的示例中,要显示在标签上的文本包含 [b] 和 [/b] 标记标签,这将使测试变为粗体。但是,要忽略此效果,文本将传递给 escape_markup() 函数。

示例

from kivy.app import App
from kivy.uix.label import Label
from kivy.utils import escape_markup
from kivy.core.window import Window

Window.size = (720,400)

class HelloApp(App):
   def build(self):
      text = 'This is an [b]important[/b] message'
      text = '[color=ff0000]' + escape_markup(text) + '[/color]'
      lbl=Label(text=text, font_size=40, markup=True)
      return lbl

HelloApp().run()

输出

Kivy Utils

get_color_from_hex()

将十六进制字符串颜色转换为 kivy 颜色。颜色属性的 RGBA 值在 0 到 1 之间。由于 RGB 值范围为 0 到 255,因此 Kivy 颜色值将数字除以 255。因此,RGB 值 50、100、200 分别表示为 50/255、100/255 和 200/255。

十六进制颜色值作为字符串给出,每个 RGB 值都有 2 个十六进制数字,并以 "#" 符号为前缀。get_color_from_hex() 函数将十六进制字符串转换为 Kivy 颜色值。

示例

from kivy.utils import *
c = get_color_from_hex("#00ff00")
print (c)

输出

[0.0, 1.0, 0.0, 1.0]

get_hex_from_color(color)

将 kivy 颜色转换为十六进制值:

示例

from kivy.utils import *
c = get_hex_from_color([0,1,0,1])
print (c)

输出

#00ff00ff

rgba()

从十六进制字符串或 0-255 值列表返回 kivy 颜色(0-1 范围内的 4 个值)。

示例

from kivy.utils import *

# from RGBA color values
c = rgba([100,150,200, 255])
print ("from RGBA:",c)

# from hex string
c = rgba('#3fc4e57f')
print ("from hex string:",c)

输出

from RGBA: [0.39215686274509803, 0.5882352941176471, 0.7843137254901961, 1.0]
from hex string: [0.24705882352941178, 0.7686274509803922, 0.8980392156862745, 0.4980392156862745]

Kivy - 检查器

Kivy 提供了一个非常有用的工具,称为 Inspector,它可以帮助您纠正使用 "kv" 脚本或以编程方式实现设计的界面时遇到的问题。Inspector 工具具有命令行界面,也可以从代码中使用。

命令行用法如下:

python main.py -m inspector

要以编程方式使用它,请在 "kivy.modules.inspector" 模块中调用 create_inspector() 函数。

from kivy.modules import inspector

class Demo(App):
   def build(self):
      button = Button(text="Test")
      inspector.create_inspector(Window, button)
      return button

显然,命令行用法更方便。让我们了解一下此工具的实用性。

假设您已经开发了一个带有 slider.py 程序的 Kivy 应用,该程序具有以下界面。

Kivy Inspector

该应用有三个滑块控件,可以帮助更改上面文本的颜色。

从命令提示符使用以下命令启动程序:

python sliderdemo.py -m inspector

将显示上述屏幕。按 ctrl+E 键显示 Inspector 工具栏。

Kivy Inspect Bar

可以将工具栏移动到顶部或底部以方便使用。单击窗口上的任何组件。宽按钮显示所单击控件的对象 ID。现在按 Parent 按钮。所选控件的父控件将被突出显示。

Kivy Inspector Highlight

双击宽按钮。它现在将显示三个带分隔符的面板以调整大小。左窗格显示控件树,中间窗格显示所选控件的所有属性,右侧窗格显示所选属性的值。

下图显示从控件树中选择了 BLUE 滑块,其属性显示在中间窗格中,并且 max 属性值显示在右侧窗格中。

Kivy Inspector Right Pane

您还可以从 Inspector 工具更改属性值。向下滚动中间窗格以找到 value 属性,并在右侧窗格的文本框中更改其值。

Kivy Inspector Text Box

Inspector 工具在对用户界面进行故障排除时非常有用。

Kivy - 工具

"kivy.tools" 模块包含一些非常有用的脚本、命令行实用程序和示例。

KV Viewer

Kivy 没有任何官方的可视化 GUI 设计器。因此,Kivy 应用的布局设计变得很繁琐,因为在创建 "kv" 文件时计划的控件放置在加载到应用中时往往不会产生预期的结果。

此 Kv-viewer 实用程序允许您动态显示 "kv" 文件,并考虑其更改。此实用程序的优点之一是,只有在您对布局满意时才能加载此 "kv" 脚本。

KViewer 是一个命令行实用程序,需要 ".kv" 文件作为参数。

python kviewer.py demo.kv

示例

以下 "kv" 文件包含 Demo 应用的设计:

TabbedPanel:
   size_hint: .8, .8
   pos_hint: {'center_x': .5, 'center_y': .5}
   do_default_tab: False
   TabbedPanelItem:
      text:"Register Tab"
      GridLayout:
         cols:2
   
         Label:
            text:"Name"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.75}
         TextInput:
            size_hint:(.4, .1)
            pos_hint:{'x':.3, 'y':.65}
   
         Label:
            text:"email"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.55}
   
         TextInput:
            size_hint:(.4, .1)
            pos_hint:{'x':.3, 'y':.45}
   
         Label:
            text:"Password"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.35}
   
         TextInput:
            password:True
            size_hint:(.4, .1)
            pos:(400, 150)
            pos_hint:{'x':.3, 'y':.25}
   
         Button:
            text:'Submit'
            size_hint : (.2, .1)
            pos_hint : {'center_x':.5, 'center_y':.09}
   
   TabbedPanelItem:
      text:'Login Tab'
      GridLayout:
         cols:2
         
         Label:
            text:"email"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.55}
   
         TextInput:
            size_hint:(.4, .1)
            pos_hint:{'x':.3, 'y':.45}
   
         Label:
            text:"Password"
            size_hint:(.2, .1)
            pos_hint:{'x':.2, 'y':.35}
   
         TextInput:
            password:True
            size_hint:(.4, .1)
            pos:(400, 150)
            pos_hint:{'x':.3, 'y':.25}
   
         Button:
            text:'Submit'
            size_hint : (.2, .1)
            pos_hint : {'center_x':.5, 'center_y':.09}

输出

让我们使用 kviewer 实用程序查看设计:

python kviewer.py demo.kv

它将生成以下输出窗口:

Kivy Kvviewer

Benchmark

此脚本执行并显示一组基准测试的结果。这些提供了一组指标,主要旨在衡量您系统的 OpenGL 性能。

要运行实用程序,请使用以下命令:

Python benchmark.py

您将获得以下 **输出**:

GL Vendor: b'Intel'
GL Renderer: b'Intel(R) Iris(R) Xe Graphics'
GL Version: b'4.6.0 - Build 31.0.101.3959'

Benchmark
---------
1/8 Core: button creation (10000 * 10 a-z) 4.246505
2/8 Core: button creation (10000 * 10 a-z), with Clock.tick [INFO ] [GL    ] NPOT texture support is available
6.612230
3/8 Core: label creation (10000 * 10 a-z) 4.543708
4/8 Core: label creation (10000 * 10 a-z), with Clock.tick 9.790683
5/8 Widget: creation (10000 Widget) 0.308506
6/8 Widget: creation (10000 Widget + 1 root) 1.734984
7/8 Widget: event dispatch (1000 on_update in 10*1000 Widget) 0.088639
8/8 Widget: empty drawing (10000 Widget + 1 root) 0.000706

Result: 27.325960

Do you want to send benchmark to gist.github.com (Y/n) : n
No benchmark posted.

Generate Icons

此工具将帮助您生成 Google Play 商店、App Store、Amazon 商店所需的所有图标。

您需要提供要从中生成图标的图像文件的名称,作为 tools 子目录中 generate-icons.py 脚本的参数。

python GPIcon.png

您将获得以下 **输出**:

Generate App store high resolution: 1024x1024
Generate App store normal resolution: 512x512
Generate iPhone (iOS 7): 120x120
Generate iPhone @2 (iOS 7): 120x120
Generate iPad (iOS 7): 76x76
Generate iPad @2 (iOS 7): 152x152
Generate iPhone (iOS >= 6.1): 57x57
Generate iPhone @2 (iOS >= 6.1): 114x114
Generate iPad (iOS >= 6.1): 72x72
Generate iPad @2 (iOS >= 6.1): 114x114
Generate iTunes Artwork (ad-hoc): 512x512
Generate iTunes Artwork @2 (ad-hoc): 1024x1024
Generate Google Play icon: 512x512
Generate Launcher icon MDPI: 48x48
Generate Launcher icon HDPI: 72x72
Generate Launcher icon XHDPI: 96x96
Generate Launcher icon XXHDPI: 48x48
Generate Launcher icon XXXHDPI: 192x192
Generate Small icon: 114x114
Generate Large icon: 512x512

此实用程序生成的图标存储在各自的文件夹中:

Kivy Tools Utility

Report Tool

此工具是用户的帮助工具。它生成在调试过程中有用的转储信息。

Python report.py

部分 **输出** 如下所示:

==============================================================
Options
==============================================================
window = ('egl_rpi', 'sdl2', 'pygame', 'sdl', 'x11')
text = ('pil', 'sdl2', 'pygame', 'sdlttf')
video = ('gstplayer', 'ffmpeg', 'ffpyplayer', 'null')
audio = ('gstplayer', 'pygame', 'ffpyplayer', 'sdl2', 'avplayer')
image = ('tex', 'imageio', 'dds', 'sdl2', 'pygame', 'pil', 'ffpy', 'gif')
camera = ('opencv', 'gi', 'avfoundation', 'android', 'picamera')
spelling = ('enchant', 'osxappkit')
clipboard = ('android', 'winctypes', 'xsel', 'xclip',
'dbusklipper', 'nspaste', 'sdl2', 'pygame', 'dummy', 'gtk3')

The report will be sent as an anonymous gist.
Do you accept to send report to https://gist.github.com/
(Y/n) : n
No report posted.
Enter any key to leave.

Kivy - 日志记录器

Python 的标准库包含 logging 模块,该模块有助于在 Python 应用中实现健壮的事件日志系统。Kivy 的日志记录机制在其基础上构建,并具有一些附加功能,例如在受支持的终端上进行颜色编码的输出、消息分类等。

每次运行 Kivy 应用时,您都会在控制台窗口中看到显示的日志。它看起来像这样:

[INFO  ] [Logger  ] Record log in C:\Users\user\.kivy\logs\kivy_23-07-10_67.txt
[INFO  ] [deps    ] Successfully imported "kivy_deps.gstreamer" 0.3.3
[INFO  ] [deps    ] Successfully imported "kivy_deps.angle" 0.3.3
[INFO  ] [deps    ] Successfully imported "kivy_deps.glew" 0.3.1
[INFO  ] [deps    ] Successfully imported "kivy_deps.sdl2" 0.6.0
[INFO  ] [Kivy    ] v2.2.0
[INFO  ] [Kivy    ] Installed at "c:\kivyenv\Lib\sitepackages\kivy\__init__.py"
[INFO  ] [Python  ] v3.11.2 (tags/v3.11.2:878ead1, Feb 7 2023, 16:38:35) [MSC v.1934 64 bit (AMD64)]
[INFO  ] [Python  ] Interpreter at "c:\kivyenv\Scripts\python.exe"
[INFO  ] [Logger  ] Purge log fired. Processing...
[INFO  ] [Logger  ] Purge finished!
[INFO  ] [Factory ] 190 symbols loaded
[INFO  ] [Image   ] Providers: img_tex, img_dds, img_sdl2, img_pil (img_ffpyplayer ignored)
[INFO  ] [Window  ] Provider: sdl2
[INFO  ] [GL      ] Using the "OpenGL" graphics system
[INFO  ] [GL      ] GLEW initialization succeeded
[INFO  ] [GL      ] Backend used <glew>
[INFO  ] [GL      ] OpenGL version <b'4.6.0 - Build 31.0.101.3959'>
[INFO  ] [GL      ] OpenGL vendor <b'Intel'>
[INFO  ] [GL      ] OpenGL renderer <b'Intel(R) Iris(R) Xe Graphics'>
[INFO  ] [GL      ] OpenGL parsed version: 4, 6
[INFO  ] [GL      ] Shading version <b'4.60 - Build 31.0.101.3959'>
[INFO  ] [GL      ] Texture max size <16384>
[INFO  ] [GL      ] Texture max units <32>
[INFO  ] [Window  ] auto add sdl2 input provider
[INFO  ] [Window  ] virtual keyboard not allowed, single mode, not docked
[INFO  ] [Text    ] Provider: sdl2
[INFO  ] [Base    ] Start application main loop
[INFO  ] [GL      ] NPOT texture support is available

这些消息会告诉你哪些硬件和驱动程序已被检测和初始化,哪些未能完成。

Kivy 库中的 Logger 类提供了一个单例实例,并在 kivy.logger 模块中定义。除了 Python 的 logging 模块中的日志记录级别(debug、info、warning、error 和 critical)之外,Kivy 还增加了一个 trace 级别。

Logger 对象具有与上述日志记录级别相对应的方法。这些方法中的每一个都接受一个字符串参数 - message - 分成两部分,用冒号 ( : ) 符号分隔。第一部分用作标题,冒号后的字符串是日志消息。

from kivy.logger import Logger

Logger.info('title: This is a info message.')
Logger.debug('title: This is a debug message.')

默认的日志记录级别在 Kivy 的配置文件中设置为 info。因此,你会看到这么长的日志输出。你可以通过 Logger 对象的 setlevel() 方法将其设置为任何所需的日志记录级别。

from kivy.logger import Logger, LOG_LEVELS

Logger.setLevel(LOG_LEVELS["debug"])

Kivy 的日志记录机制由一个环境变量 KIVY_LOG_MODE 控制,它有三个可能的值:KIVY、PYTHON、MIXED。

默认的 KIVY_LOG_MODE 是 KIVY,因此系统中的所有日志消息都会输出到 Kivy 日志文件和控制台。

如果你将其设置为 PYTHON 模式,则不会添加任何处理程序,并且不会捕获 sys.stderr 输出。它留给客户端添加合适的处理程序。

在 MIXED 模式下,处理程序直接添加到 Kivy 的 Logger 对象中,并且关闭传播。sys.stderr 没有被重定向。

所有与日志记录相关的配置参数都可以在 config.ini 文件的 [Kivy] 部分找到。参数及其默认值如下:

log_dir = logs
log_enable = 1
log_level = info
log_name = kivy_%y-%m-%d_%_.txt
log_maxfiles = 100

请注意,即使日志记录器未启用,你也可以访问最后 100 个 LogRecords。

from kivy.logger import LoggerHistory
print(LoggerHistory.history)

Kivy - 帧缓冲区

Kivy 库提供了一个名为“Fbo”的类,它代表帧缓冲区离屏。它是一个离屏窗口,你可以在上面绘制任何图形指令,然后将其用作某个 Kivy 窗口小部件画布的纹理。

Fbo 类在 kivy.graphics.fbo 模块中定义。第一步是创建 fbo 并将 fbo 纹理用于其他矩形。

from kivy.graphics import Fbo, Color, Rectangle
with self.canvas:
   self.fbo = Fbo(size=self.size)

接下来,将图形指令(如 Rectangle)添加到 Fbo 对象。例如:

with self.fbo:
   Color(1, 0, 0, .8)
   Rectangle(size=(256, 64))
   Color(0, 1, 0, .8)
   Rectangle(size=(64, 256))

最后,将 Fbo 纹理应用于画布。

self.texture = self.fbo.texture

请注意,如果 OpenGL 上下文丢失,则 FBO 会丢失。在这种情况下,你需要使用 Fbo.add_reload_observer() 方法重新上传数据。

add_reload_observer(callback) − 在整个图形上下文重新加载后,添加一个要调用的回调函数。回调参数将是上下文本身。

bind() 方法将 FBO 对象绑定到当前的 opengl 上下文。这样,所有绘图操作都将在帧缓冲区内进行,直到调用 release() 为止。release() 方法释放或解除帧缓冲区的绑定。

self.fbo = FBO()
self.fbo.bind()

# do any drawing command
self.fbo.release()

self.canvas = self.fbo.texture

还有一个 remove_reload_observer(callback) 方法,它从观察者列表中删除之前由 add_reload_observer() 添加的回调函数。

clear_buffer()clear_color 方法清除帧缓冲区和清除颜色,颜色格式为 (red, green, blue, alpha)。

示例

以下代码演示了在 Kivy 应用程序中使用帧缓冲区的方法。代码的重要部分是一个名为 FboFloatLayout 的类,它继承了 Kivy 的 FloatLayout 类。

构造函数(__init__() 方法)在浮动布局的画布上创建一个 Fbo 对象,在其上绘制一个矩形,并将其纹理设置为画布的纹理。

def __init__(self, **kwargs):
   self.canvas = Canvas()
   with self.canvas:
      self.fbo = Fbo(size=self.size)
      self.fbo_color = Color(1, 1, 1, 1)
      self.fbo_rect = Rectangle()
   
   with self.fbo:
      ClearColor(0, 0, 0, 0)
      ClearBuffers()
   
   self.texture = self.fbo.texture
   super(FboFloatLayout, self).__init__(**kwargs)

我们将向此 FloatLayout 类添加一个按钮,但在添加之前,需要重写 add_widget() 方法,以便将图形指令添加到 fbo 中,然后将其添加到画布中。

def add_widget(self, *args, **kwargs):
   canvas = self.canvas
   self.canvas = self.fbo
   ret = super(FboFloatLayout, self).add_widget(*args, **kwargs)
   self.canvas = canvas
   return ret

FboFloatLayout 类还具有响应大小、位置和纹理变化的回调函数。

def on_size(self, instance, value):
   self.fbo.size = value
   self.texture = self.fbo.texture
   self.fbo_rect.size = value

def on_pos(self, instance, value):
   self.fbo_rect.pos = value

def on_texture(self, instance, value):
   self.fbo_rect.texture = value

现在到 App 类。build() 方法添加一个按钮并应用一系列动画效果,这些效果会反复更改按钮的位置,从上到下,从右到左。

def anim_btn(*args):
   animate = Animation(pos=(b.pos[0], Window.height - 50))
   animate += Animation(pos=(b.pos[0], 0))
   animate += Animation(pos_hint={'center_x': 1})
   animate += Animation(pos_hint={'center_x': 0})
   animate += Animation(pos_hint={'center_x': .5})
   animate.start(b)
   animate.repeat = True
   
b.bind(on_press=anim_btn)

为了方便起见,这些代码片段都放在了完整代码列表中。

from kivy.graphics import Color, Rectangle, Canvas,
ClearBuffers, ClearColor
from kivy.graphics.fbo import Fbo
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty, NumericProperty
from kivy.app import App
from kivy.animation import Animation
from kivy.core.window import Window

Window.size = (720, 400)

class FboFloatLayout(FloatLayout):
   texture = ObjectProperty(None, allownone=True)

   def __init__(self, **kwargs):
      self.canvas = Canvas()
      with self.canvas:
         self.fbo = Fbo(size=self.size)
         self.fbo_color = Color(1, 1, 1, 1)
         self.fbo_rect = Rectangle()

      with self.fbo:
         ClearColor(0, 0, 0, 0)
         ClearBuffers()
         
      self.texture = self.fbo.texture
      super(FboFloatLayout, self).__init__(**kwargs)

   def add_widget(self, *args, **kwargs):
      canvas = self.canvas
      self.canvas = self.fbo
      ret = super(FboFloatLayout, self).add_widget(*args, **kwargs)
      self.canvas = canvas
      return ret
      
   def on_size(self, instance, value):
      self.fbo.size = value
      self.texture = self.fbo.texture
      self.fbo_rect.size = value

   def on_pos(self, instance, value):
      self.fbo_rect.pos = value

   def on_texture(self, instance, value):
      self.fbo_rect.texture = value

class FBOdemoApp(App):
   def build(self):
      f = FboFloatLayout()
      b = Button(text="FBO", size_hint=(None, None), pos_hint={'center_x': .5})
      f.add_widget(b)

      def anim_btn(*args):
         animate = Animation(pos=(b.pos[0], Window.height - 50))
         animate += Animation(pos=(b.pos[0], 0))
         animate += Animation(pos_hint={'center_x': 1})
         animate += Animation(pos_hint={'center_x': 0})
         animate += Animation(pos_hint={'center_x': .5})
         animate.start(b)
         animate.repeat = True
      b.bind(on_press=anim_btn)
      return f
      
FBOdemoApp().run()

输出

应用程序以底部带有 FBO 标题的按钮开始。单击时,它将开始执行定义的动画效果。

kivy framebuffer

Kivy - 绘图应用

在本章中,我们将学习开发一个简单的 Kivy 应用程序,该应用程序允许用户通过拖动鼠标按钮来绘制不同尺寸和颜色的填充矩形和圆形(椭圆)。

用户从预期矩形/椭圆的左上角拖动鼠标指针到右下角。以下代码开发中使用的方法是在 touch_down 事件和 touch_up 事件中捕获鼠标坐标。因此,我们在 App 类的 build() 方法中用此代码启动程序:

def build(self):
   self.w= Widget()
   self.w.bind(on_touch_down=self.on_touch_down)
   self.w.bind(on_touch_up=self.on_touch_up)
   return(self.w)

我们希望允许用户选择绘制矩形或圆形。需要添加三个切换按钮,用于选择绘制哪种形状或清除画布。因此,我们将 App 窗口的布局更改为 box 布局,在顶部添加 Widget 对象,并在其下方放置三个按钮。

lo = BoxLayout(orientation='vertical')

self.w = Widget()
self.w.bind(on_touch_down=self.on_touch_down)
self.w.bind(on_touch_up=self.on_touch_up)
lo.add_widget(self.w)

hlo = BoxLayout(orientation='horizontal', size_hint=(1, .1))

self.b2 = ToggleButton(text='Circle', group='mode')
self.b1 = ToggleButton(text='Rectangle', state='down', group='mode')
self.b3 = ToggleButton(text='Clear', group='mode')

hlo.add_widget(self.b1)
hlo.add_widget(self.b2)
hlo.add_widget(self.b3)
lo.add_widget(hlo)

return lo

要绘制所需的形状,我们需要在触碰按下和触碰抬起时捕获鼠标位置。

on_touch_down() 方法很简单:

def on_touch_down(self, *args):
   self.td = args[1].pos

所有处理都在 on_touch_up() 方法中进行。捕获的按下事件和抬起事件的坐标用于计算矩形或圆形的尺寸。

对于圆形,x 半径、y 半径和中心计算如下:

self.tu=args[1].pos
   xr = (self.tu[0]-self.td[0])/2
   yr = (self.td[1]-self.tu[1])/2
   c=(self.td[0]+xr, self.td[1]-yr)

我们需要宽度和高度以及绘制矩形的左上角坐标。self.td 元组给出左上角点,xr*2 给出宽度,yr*2 给出高度。

形状绘制在窗口小部件画布上。我们选择随机颜色进行绘制。按下按钮的 text 属性告诉我们绘制的形状:

color = (random(), random(), random())
with self.w.canvas:
   Color(*color)
   if self.btn=='Rectangle':
      Rectangle(pos=self.td, size=(xr*2,yr*2))
   if self.btn=='Circle':
      Ellipse(pos=(c), size=(xr,yr))

三个切换按钮绑定到一个方法。如果标题是 Clear,则清除窗口小部件画布。

def clear_canvas(self, instance, value):
   self.btn = instance.text
   self.press = True
   if value == 'down' and self.btn == 'Clear':
      self.w.canvas.clear()
   return True

示例

应用程序的完整代码如下:

from random import random
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Ellipse, Line, Rectangle
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.boxlayout import BoxLayout
from kivy.core.window import Window

Window.size = (720, 400)

class MyPaintApp(App):
   def clear_canvas(self, instance, value):
      self.btn = instance.text
      self.press = True
      if value == 'down' and self.btn == 'Clear':
         self.w.canvas.clear()
      return True

   def on_touch_down(self, *args):
      self.td = args[1].pos

   def on_touch_up(self, *args):
      if self.press == True:
         self.press = False
         return True
      self.tu = args[1].pos
      xr = (self.tu[0] - self.td[0]) / 2
      yr = (self.td[1] - self.tu[1]) / 2
      c = (self.td[0] + xr, self.td[1] - yr)
      color = (random(), random(), random())
      with self.w.canvas:
         Color(*color)
         if self.btn == 'Rectangle':
            Rectangle(pos=self.td, size=(xr * 2, yr * 2))
         if self.btn == 'Circle':
            Ellipse(pos=(c), size=(xr, yr))

   def build(self):
      self.press = False
      self.btn = 'Rectangle'
      
      lo = BoxLayout(orientation='vertical')
      
      self.w = Widget()
      self.w.bind(on_touch_down=self.on_touch_down)
      self.w.bind(on_touch_up=self.on_touch_up)
      lo.add_widget(self.w)
      
      hlo = BoxLayout(orientation='horizontal', size_hint=(1, .1))
      
      self.b2 = ToggleButton(text='Circle', group='mode')
      self.b1 = ToggleButton(text='Rectangle', state='down', group='mode')
      self.b3 = ToggleButton(text='Clear', group='mode')
      self.b1.bind(state=self.clear_canvas)
      self.b2.bind(state=self.clear_canvas)
      self.b3.bind(state=self.clear_canvas)
      
      hlo.add_widget(self.b1)
      hlo.add_widget(self.b2)
      hlo.add_widget(self.b3)
      lo.add_widget(hlo)
      
      return lo
      
MyPaintApp().run()

输出

运行以上代码。选择要绘制的形状。从左上角拖动鼠标到右下角。矩形/圆形以随机颜色绘制在不同的位置。

Kivy Drawing App

单击“清除”按钮清除画布上的图形。

Kivy - 计算器应用

在本章中,我们将学习使用 Kivy 库构建一个计算器应用程序。计算器包含每个数字和运算符的按钮。它应该有一个带有“=”标题的按钮来计算运算,还有一个按钮来清除结果。

让我们从以下设计开始:

Kivy Calculator App

以上布局显示顶部有一个输入框,后面跟着一个用于按钮的 3 列布局,除此之外,四个运算符按钮排列在一列中。

我们将使用一个顶部网格布局,该布局有一列,并在其下方添加一个右对齐的 TextInput,在 TextInput 下方我们将放置另一个 2 列网格。此网格的左侧单元格包含三列中的数字、= 和 C 按钮。第二列是另一个用于所有算术运算符的一列网格。

以下“kv”文件采用了此逻辑:

<calcy>:
   GridLayout:
      cols:1
      TextInput:
         id:t1
         halign:'right'
         size_hint:1,.2
         font_size:60
      GridLayout:
         cols:2
         GridLayout:
            cols:3
            size:root.width, root.height
            Button:
               id:one
               text:'1'
               on_press:root.onpress(*args)
            Button:
               id:two
               text:'2'
               on_press:root.onpress(*args)
            Button:
               id:thee
               text:'3'
               on_press:root.onpress(*args)
            Button:
               id:four
               text:'4'
               on_press:root.onpress(*args)
            Button:
               id:five
               text:'5'
               on_press:root.onpress(*args)
            Button:
               id:six
               text:'6'
               on_press:root.onpress(*args)
            Button:
               id:seven
               text:'7'
               on_press:root.onpress(*args)
            Button:
               id:eight
               text:'8'
               on_press:root.onpress(*args)
            Button:
               id:nine
               text:'9'
               on_press:root.onpress(*args)
            Button:
               id:zero
               text:'0'
               on_press:root.onpress(*args)
            Button:
               id:eq
               text:'='
               on_press:root.onpress(*args)
            Button:
               id:clr
               text:'C'
               on_press:root.onpress(*args)
      GridLayout:
         cols:1
         size_hint:(.25, root.height)
         Button:
            id:plus
            text:'+'
            on_press:root.onpress(*args)
         Button:
            id:minus
            text:'-'
            on_press:root.onpress(*args)
         Button:
            id:mult
            text:'*'
            on_press:root.onpress(*args)
         Button:
            id:divide
            text:'/'
            on_press:root.onpress(*args)

请注意,每个按钮都在其 on_press 事件上绑定了 onpress() 方法。

onpress() 方法基本上读取按钮标题(其 text 属性)并决定操作过程。

  • 如果按钮的标题是数字,则将其附加到 TextInput 框的 text 属性。

self.ids.t1.text=self.ids.t1.text+instance.text
  • 如果按钮表示任何算术运算符(+、-、*、/),则此时文本框中的数字将存储在一个变量中以供进一步操作,并且文本框将被清除。

if instance.text in "+-*/":
self.first=int(self.ids.t1.text)
self.ids.t1.text='0'
self.op=instance.text
  • 如果按钮的 text 属性是“=”,则此时文本框中的数字是第二个操作数。执行适当的算术运算,并将结果显示在文本框中。

if instance.text=='=':
if self.first==0: return
self.second=int(self.ids.t1.text)
if self.op=='+': result=self.first+self.second
if self.op=='-': result=self.first-self.second
if self.op=='*': result=self.first*self.second
if self.op=='/': result=self.first/self.second
self.ids.t1.text=str(result)
self.first=self.second=0
  • 最后,如果按钮的标题是 C,则文本框将设置为为空。

if instance.text=='C':
self.ids.t1.text=''
self.first=self.second=0

示例

以上“kv”文件加载到 App 类的 build() 方法中。以下是完整代码

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.core.window import Window

Window.size = (720,400)

class calcy(GridLayout):
   def __init__(self, **kwargs):
      super(calcy, self).__init__(**kwargs)
      self.first=self.second=0

   def onpress(self, instance):
      if instance.text=='C':
         self.ids.t1.text=''
         self.first=self.second=0
      elif instance.text in "+-*/":
         self.first=int(self.ids.t1.text)
         self.ids.t1.text='0'
         self.op=instance.text

      elif instance.text=='=':
         if self.first==0: return
            self.second=int(self.ids.t1.text)
         if self.op=='+': result=self.first+self.second
         if self.op=='-': result=self.first-self.second
         if self.op=='*': result=self.first*self.second
         if self.op=='/': result=self.first/self.second
         self.ids.t1.text=str(result)
         self.first=self.second=0
      else:
         self.ids.t1.text=self.ids.t1.text+instance.text

class calculatorapp(App):
   def build(self):
      return calcy(cols=3)
      
calculatorapp().run()

输出

运行以上程序并使用此应用程序执行所有基本的算术计算。

Kivy Calculator

Kivy - 计时器应用

在本章中,我们将使用 Python 的 Kivy GUI 框架构建一个秒表应用程序。秒表测量从开始事件到停止事件之间经过的时间。例如,用于测量运动员完成 100 米跑所需时间的秒表。

应用程序的 GUI 设计应类似于下图:

Kivy Stopwatch App

以下“kv”脚本用于放置两个标签和两个按钮,如上图所示。顶部标签将用于显示当前时间,底部标签将用于显示计时器启动后经过的时间。左侧按钮用于启动/停止秒表。右侧的按钮将计时器重置为 0。

BoxLayout:
   orientation: 'vertical'
   
   Label:
      id: time
      font_size:40
      markup:True
      text: '[b]00[/b]:00:00'
   
   BoxLayout:
      orientation: 'horizontal'
      padding: 20
      spacing: 20
      height: 90
      size_hint: (1, None)
      
      Button:
         text: 'Start'
         id: start_stop
         on_press : app.start_stop()
         
      Button:
         id: reset
         text: 'Reset'
         on_press: app.reset()
   Label:
      id: stopwatch
      font_size:40
      markup:True
      text:'00:00.[size=40]00[/size]'

在应用程序代码中,我们定义了一个 on_stop() 事件处理程序,该处理程序在 GUI 填充后立即调用。它安排一个时间事件处理程序,该处理程序每秒更新一次标签的当前时间。

def on_start(self):
   Clock.schedule_interval(self.update_time, 0)

update_time() 方法更新显示在上标签(id 为 time)上的当前时间,以及显示在下标签(id 为 stopwatch)上的经过时间 - 如果计时器已启动。

def update_time(self, t):
   if self.sw_started:
      self.sw_seconds += t
   minutes, seconds = divmod(self.sw_seconds, 60)
   part_seconds = seconds * 100 % 100
   self.root.ids.stopwatch.text = "{:2d} : {:2d}.{:2d}".format(int(minutes), int(seconds), int(part_seconds))
   self.root.ids.time.text = strftime('[b]%H[/b]:%M:%S')

id 为 start 的按钮绑定到 start_stop() 方法,该方法存储秒表的状态(已启动还是已停止),并相应地更新 start 按钮(id 为 start_stop)的标题。

def start_stop(self):
   self.root.ids.start_stop.text = 'Start' if
self.sw_started else 'Stop'
   self.sw_started = not self.sw_started

右侧的按钮将时间计数器重置为 0,将另一个按钮的标题设置为 start,并将底部标签的标题重置为 0:0.0。

def reset(self):
   if self.sw_started:
      self.root.ids.start_stop.text = 'Start'
      self.sw_started = False
   self.sw_seconds = 0

示例

整个编码逻辑在以下代码中给出:

from time import strftime
from kivy.clock import Clock
from kivy.app import App
from kivy.core.window import Window

Window.size = (720, 400)

class StopWatchApp(App):
   sw_seconds = 0
   sw_started = False

   def update_time(self, t):
      if self.sw_started:
         self.sw_seconds += t
      minutes, seconds = divmod(self.sw_seconds, 60)
      part_seconds = seconds * 100 % 100

      self.root.ids.stopwatch.text = "{:2d} : {:2d}.{:2d}".format(int(minutes), int(seconds), int(part_seconds))
      self.root.ids.time.text = strftime('[b]%H[/b]:%M:%S')

   def on_start(self):
      Clock.schedule_interval(self.update_time, 0)
      
   def start_stop(self):
      self.root.ids.start_stop.text = 'Start' if self.sw_started else 'Stop'
      self.sw_started = not self.sw_started

   def reset(self):
      if self.sw_started:
         self.root.ids.start_stop.text = 'Start'
         self.sw_started = False
      self.sw_seconds = 0
      
StopWatchApp().run()

输出

由于以上代码中 App 类的名称是 StopWatchApp,因此其“kv”语言设计脚本必须命名为“StopWatch.kv”,然后运行程序。

Kivy Stopwatch App Example

按下“开始”按钮。计数器将开始,在底部标签上显示经过的时间。标题变为“停止”。再次按下它以停止时钟运行。单击“重置”按钮将标签恢复为“0”。

Kivy Stopwatch App Start

Kivy - 相机处理

Kivy 框架通过特定于平台的提供程序支持相机硬件。“opncv-python”包可在大多数操作系统上为 Kivy 启用相机支持。因此,建议在 Kivy 的工作环境中安装 opencv-python 包。

在本章中,我们将使用 Kivy 库的 Camera 类构建一个相机应用程序。相机窗口小部件以及一个 ToggleButton 和一个普通按钮放置在垂直框布局中以构建应用程序界面。

Camera 实例以初始播放状态 True 开始,这意味着应用程序窗口将在加载后立即从相机启动视频流。当 Toggle 按钮按下时,它会停止相机。它绑定到 play() 方法。仅当相机正在播放时,Capture 按钮才处于启用状态。

def play(self, instance):
   if instance.state=='down':
      self.mycam.play=False
      instance.text='Play'
      self.cb.disabled=True
   else:
      self.mycam.play=True
      instance.text='Stop'
      self.cb.disabled=False

Capture 按钮通过调用 Camera 对象的 export_to_png() 方法将当前帧保存到 PNG 文件。

捕获图像后,Kivy 会弹出一个标题为“图像已捕获”的消息框。

def capture(self, instance):
   if self.tb.text == 'Stop':
      self.mycam.export_to_png("IMG.png")
      layout = GridLayout(cols=1)

      popup = Popup(
         title='Image Captured', title_size=24,
         content=layout, auto_dismiss=True,
         size_hint=(None, None), size=(300, 100)
      )
   popup.open()

其余代码涉及在 build() 方法中组合应用程序界面。

示例

完整的代码如下:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.camera import Camera
from kivy.core.window import Window

Window.size = (720, 400)

class TestCamera(App):
   def build(self):
      box = BoxLayout(orientation='vertical')
      self.mycam = Camera(play=True, resolution=(640, 480))

      box.add_widget(self.mycam)
      self.tb = ToggleButton(
         text='Stop', size_hint_y=None,
         height='48dp', on_press=self.play
      )
      box.add_widget(self.tb)

      self.cb = Button(
         text='Capture', size_hint_y=None, height='48dp',
         disabled=False, on_press=self.capture
      )
      box.add_widget(self.cb)
      return box
      
   def play(self, instance):
      if instance.state == 'down':
         self.mycam.play = False
         instance.text = 'Play'
         self.cb.disabled = True
      else:
         self.mycam.play = True
         instance.text = 'Stop'
         self.cb.disabled = False
         
   def capture(self, instance):
      if self.tb.text == 'Stop':
         self.mycam.export_to_png("IMG.png")
         layout = GridLayout(cols=1)

         popup = Popup(
            title='Image Captured', title_size=24,
            content=layout, auto_dismiss=True,
            size_hint=(None, None), size=(300, 100)
         )
      popup.open()
      
TestCamera().run()

输出

应用程序启动时,相机实例加载。请注意,根据系统和相机设备,启动可能需要几秒钟。切换按钮的标题为“停止”,捕获按钮已启用。

Kivy Camera Handling

如果按下“停止”按钮,则捕获按钮将被禁用。处于启用状态时,按下“捕获”按钮。当前帧将保存为“img.png”,并弹出一个弹出框。

Kivy Image Captured

Kivy - 图片查看器

在本章中,我们将使用 Kivy 构建一个简单的图像查看器应用程序。以下代码使用 Kivy 的 Image 窗口小部件和按钮来浏览所选文件夹中图像列表。还有一个按钮可以打开一个 FileChooser,让用户选择不同的文件夹以查看其中的图像。

首先,我们需要构建当前所选文件夹中所有图像文件的列表。为此,我们使用 os.listdir() 方法。

import os
self.idx=0
self.fillist=[]
dir_path = '.'
for file in os.listdir(dir_path):
   if file.endswith('.png'):
      self.fillist.append(file)

应用程序界面的构建涉及一个垂直框布局,其中 Image 对象放置在顶部,另一个水平框布局包含三个按钮。

lo=BoxLayout(orientation='vertical')
self.img= Image(source=self.fillist[self.idx])
lo.add_widget(self.img)

hlo=BoxLayout(orientation='horizontal', size_hint=(1, .1))
self.b1 = Button(text = 'Dir', on_press=self.onButtonPress)
self.b2 = Button(text = 'Prev', on_press=self.previmg)
self.b3 = Button(text = 'Next', on_press=self.nextimg)

hlo.add_widget(self.b1)
hlo.add_widget(self.b2)
hlo.add_widget(self.b3)
lo.add_widget(hlo)

图像部件(Image widget)开始时显示第一个可用的图像。当点击标题为“Next”的按钮时,指向文件列表的索引会递增;当点击“Previous”按钮时,索引会递减,并在图像对象中加载索引的图像文件。

def previmg(self, instance):
   self.idx-=1
   if self.idx<0: self.idx=0
   self.img.source=self.fillist[self.idx]
   
def nextimg(self, instance):
   self.idx+=1
   if self.idx>=len(self.fillist):
self.idx=len(self.fillist)-1
   self.img.source=self.fillist[self.idx]

第三个按钮(标题为Dir)会弹出一个文件选择器对话框。您可以选择所需的文件夹进行查看。

def onButtonPress(self, button):
   layout=GridLayout(cols=1)
   fw=FileChooserListView(dirselect=True, filters=["!*.sys"])
   fw.bind(on_selection=self.onselect)
   closeButton = Button(
      text = "OK", size_hint=(None, None),
      size=(100,75)
   )
   layout.add_widget(fw)
   layout.add_widget(closeButton)
   self.popup = Popup(
      title='Choose Folder', content=layout,
      auto_dismiss=False, size_hint=(None, None),
      size=(400, 400)
   )
   self.popup.open()

示例

当前文件夹中的图像会填充文件列表作为开始。以下是完整的代码:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.filechooser import FileChooserListView
from kivy.core.window import Window

Window.size = (720, 400)

class ImageViewerApp(App):
   def previmg(self, instance):
      self.idx -= 1
      if self.idx < 0: self.idx = 0
      self.img.source = self.fillist[self.idx]

   def nextimg(self, instance):
      self.idx += 1
      if self.idx >= len(self.fillist): self.idx = len(self.fillist) - 1
      self.img.source = self.fillist[self.idx]

   def onButtonPress(self, button):
      layout = GridLayout(cols=1)
      fw = FileChooserListView(dirselect=True, filters=["!*.sys"])
      fw.bind(on_selection=self.onselect)
      closeButton = Button(
         text="OK", size_hint=(None, None), size=(100, 75)
      )
      layout.add_widget(fw)
      layout.add_widget(closeButton)
      self.popup = Popup(
         title='Choose Folder', content=layout,
         auto_dismiss=False, size_hint=(None, None), size=(400, 400)
      )
      self.popup.open()
      closeButton.bind(on_press=self.on_close)
      
   def onselect(self, *args):
      print(args[1][0])
      
   def on_close(self, event):
      self.popup.dismiss()
      
   def build(self):
      import os
      self.idx = 0
      self.fillist = []
      dir_path = '.'
      for file in os.listdir(dir_path):
         if file.endswith('.png'):
            self.fillist.append(file)
      lo = BoxLayout(orientation='vertical')
      self.img = Image(source=self.fillist[self.idx])
      lo.add_widget(self.img)
      
      hlo = BoxLayout(orientation='horizontal', size_hint=(1, .1))
      
      self.b1 = Button(text='Dir', on_press=self.onButtonPress)
      self.b2 = Button(text='Prev', on_press=self.previmg)
      self.b3 = Button(text='Next', on_press=self.nextimg)
      hlo.add_widget(self.b1)
      hlo.add_widget(self.b2)
      hlo.add_widget(self.b3)
      lo.add_widget(hlo)
      return lo
ImageViewerApp().run()

输出

运行此代码时,它将显示索引为“0”的图像:

Kivy Image Viewer

点击导航按钮向前或向后浏览。点击“Dir”按钮选择新的文件夹。

Kivy Navigation Buttons

Kivy - 贝塞尔曲线

在本章中,我们将创建一个Kivy应用程序,该应用程序将沿着点列表交互式地绘制一条贝塞尔曲线。如果使用以下代码段计算出的x和y坐标绘制闭合线,则生成的图形类似于吃豆人角色:

from math import cos, sin, radians

x = y = 300
z = 200

self.points = [x, y]
for i in range(45, 360, 45):
   i = radians(i)
   self.points.extend([x + cos(i) * z, y + sin(i) * z])

   with self.layout.canvas:
      Color(1.0, 0.0, 1.0)
      self.line = Line(
         points=self.points + self.points[:2],
         dash_offset=10, dash_length=100
      )

它生成如下所示的线型图案:

Kivy Bezier

然后,我们使用相同的点列表绘制一条贝塞尔曲线。

Color(1.0, 0.0, 1.0)
self.line = Line(
   points=self.points + self.points[:2],
   dash_offset=0,
   dash_length=100
)

线条和贝塞尔曲线将显示如下:

Kivy Bezier Line

现在我们想动态构建这两条曲线。我们使用两个滑块来更改线指令和贝塞尔指令的虚线长度和偏移值,因为每个滑块的值属性会随着以下事件处理程序的变化而变化:

def _set_bezier_dash_offset(self, instance, value):
   # effect to reduce length while increase offset
   self.bezier.dash_length = 100 - value
   self.bezier.dash_offset = value
   
def _set_line_dash_offset(self, instance, value):
   # effect to reduce length while increase offset
   self.line.dash_length = 100 - value
   self.line.dash_offset = value

因此,更改滑块值以查看两条曲线如何动态重新绘制。

示例

完整代码如下所示:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.graphics import Color, Bezier, Line
from kivy.core.window import Window

Window.size = (720,400)

class Main(App):
   title='Bezier Example'
   def _set_bezier_dash_offset(self, instance, value):
      # effect to reduce length while increase offset
      self.bezier.dash_length = 100 - value
      self.bezier.dash_offset = value
      
   def _set_line_dash_offset(self, instance, value):
      # effect to reduce length while increase offset
      self.line.dash_length = 100 - value
      self.line.dash_offset = value
   
   def build(self):
      from math import cos, sin, radians
      x = y = 300
      z = 200
      # Pacman !
      self.points = [x, y]
      for i in range(45, 360, 45):
         i = radians(i)
         self.points.extend([x + cos(i) * z, y + sin(i) * z])
      print (self.points)
      self.layout = FloatLayout()
      
      with self.layout.canvas:
         Color(1.0, 0.0, 0.0)
   
         self.bezier = Bezier(
            points=self.points, segments=150, loop=True,
            dash_length=100, dash_offset=10
         )
   
         Color(1.0, 0.0, 1.0)
         self.line = Line(
            points=self.points + self.points[:2],
            dash_offset=10,
            dash_length=100)
      l1=Label(
         text='Beizer offset', pos_hint={'x':.1},
         size_hint=(.1, None), height=50
      )
      self.layout.add_widget(l1)
   
      s1 = Slider(
         y=0, pos_hint={'x': .3},
         size_hint=(.7, None), height=50
      )
      self.layout.add_widget(s1)
      s1.bind(value=self._set_bezier_dash_offset)
      
      l2=Label(
         text='Line offset', y=50, pos_hint={'x':.1},
         size_hint=(.1, None), height=50
      )
      self.layout.add_widget(l2)
      
      s2 = Slider(
         y=50, pos_hint={'x': .3},
         size_hint=(.7, None), height=50
      )
      self.layout.add_widget(s2)
      s2.bind(value=self._set_line_dash_offset)
      
      return self.layout

if __name__ == '__main__':
   Main().run()

输出

以下屏幕截图显示了在两个不同滑块位置下的曲线:

Kivy Bezier Curves Kivy Bezier Offset

Kivy - 画布压力测试

在本章中,我们将找出图形引擎的效率如何。这可以通过向Kivy对象的画布添加大量图形指令来完成。

下面给出的程序测量图形引擎在随机位置和随机选择的颜色值下绘制一万个矩形所需的时间。因此,它将评估画布可以承受多少压力。

语法

矩形指令在任何Kivy部件的画布上绘制,语法如下:

with self.root.canvas:
   Color(r,g,b,a)
   Rectangle(pos, size)

我们将为RGB以及矩形的x和y坐标生成随机值,并显示10,000个矩形。所需时间由perf_counter()函数计算。

t1=perf_counter()
with self.root.canvas:
   for i in range(10000):
      Color(r,g,b,a)
      Rectangle(pos, size)
      t2=perf_counter()

绘制所需的时间将是“t2 - t1”。它将显示在Kivy应用程序窗口的标题栏上。

示例

完整的程序如下:

from kivy.app import App
from kivy.graphics import *
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from random import random as r
from time import perf_counter
from kivy.core.window import Window

Window.size = (720, 400)

class StresstestApp(App):
   def add_rects(self, *args):
      self.t1 = perf_counter()
      with self.root.canvas:
         for i in range(10000):
            Color(r(), 1, 1, mode='hsv')
            Rectangle(
               pos=(r() * self.root.width + self.root.x, r() * self.root.height + self.root.y),
               size=(20, 20)
            )
            self.t2 = perf_counter()
      self.title = str(self.t2 - self.t1) + "Sec. to draw 10000 rectangles"
      
   def build(self):
      main = GridLayout(cols=1)
      self.root = FloatLayout(size=(Window.width, 100))
      self.btn1 = Button(
         text='start', size_hint=(1, None),
         pos_hint={'center_x': .5, 'center_y': .1}
      )
      self.btn1.bind(on_press=self.add_rects)
      self.root.add_widget(self.btn1)
      main.add_widget(self.root)
      return main
      
StresstestApp().run()

输出

运行程序并点击“start”按钮。

Kivy Canvas Stress

您将在标题栏上看到绘制所需的时间,如下图所示:

Kivy Canvas Stress Title Bar

Kivy - 圆形绘制

在本章中,我们将动态地在Kivy应用程序窗口上绘制一个圆形。其思想是使用四个Kivy滑块部件设置圆形的宽度、高度和中心点值,并在盒布局画布上刷新大小和位置。

“kivy.graphics”模块包含Ellipse类。圆形实际上是宽度和高度相等的椭圆。

语法

在任何部件的画布上绘制椭圆的语法如下:

with widget.canvas:
   Color(1, 1, 1)
   Ellipse(
      pos=(w, h), size=(cx, cy),
      angle_start=0, angle_end=360
)

滑块用于选择“w”、“h”、“cx”和“cy”的值。我们应用程序窗口的预期外观如下:

Kivy Circle Drawing

主布局使用垂直盒布局。它包括两个水平盒,每个盒包含两个滑块和两个标签。在第一个盒中放置了宽度和高度选择器;在第二个盒中,可以选择椭圆的X和Y坐标。然后,顶部垂直盒添加了一个FloatLayout,其画布是绘制的目标。

以下“kv”文件组装了上述部件结构。

示例

BoxLayout:
   orientation: 'vertical'
   BoxLayout:
      size_hint_y: None
      height: sp(50)
      BoxLayout:
         orientation: 'horizontal'
         Slider:
            id: wd
            min: 100
            max: 300
            value: 200
         Label:
            text: 'Width: {}'.format(int(wd.value))
         Slider:
            id: ht
            min: 100
            max: 300
            value: 200
         Label:
            text: 'Height: {}'.format(int(ht.value))
   BoxLayout:
      size_hint_y: None
      height: sp(50)
      BoxLayout:
         orientation: 'horizontal'
         Slider:
            id: cx
            min: 10
            max: 600
            value: 360
         Label:
            text: 'cx: {}'.format(int(cx.value))
         Slider:
            id: cy
            min: 10
            max: 300
            value: 50
         Label:
            text: 'cy: {}'.format(int(cy.value))

   FloatLayout:
      canvas:
         Color:
            rgb: 1, 1, 1
         Ellipse:
            pos: cx.value, cy.value
            size: wd.value, ht.value
            angle_start: 0
            angle_end: 360

您需要做的就是在我们的Kivy App代码中加载此“kv”文件。在build()方法内部调用“kivy.lang.Builder”类的load_file()方法。

from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window

Window.size = (720,400)
class CircleApp(App):
   def build(self):
      return Builder.load_file('circledemo.kv')
   
CircleApp().run()

就是这样。运行程序,您将看到圆形绘制在起始位置。滑动控件以获取不同的值,并查看圆形如何改变其位置和大小。

如果您希望使用纯Python代码来组合应用程序窗口外观,您可以通过手动将所需的部件放置在build()方法内部来实现,如下所示:

def build(self):
   # main layout
   lo = BoxLayout(orientation='vertical', size=Window.size)
   
   # for width and height sliders
   sliders_wh = BoxLayout(size_hint_y=None, height=50)

   slb1 = BoxLayout(orientation='horizontal')
   self.sl1 = Slider(min=100, max=300, value=200)
   l1 = Label(text='Width: {}'.format(int(self.sl1.value)))
   slb1.add_widget(self.sl1)
   slb1.add_widget(l1)
   self.sl2 = Slider(min=100, max=300, value=200)
   l2 = Label(text='Height: {}'.format(int(self.sl2.value)))
   slb1.add_widget(self.sl2)
   slb1.add_widget(l2)
   sliders_wh.add_widget(slb1)
   
   # for cx and cy sliders
   sliders_xy = BoxLayout(size_hint_y=None, height=50)
   slb2 = BoxLayout(orientation='horizontal')
   self.sl3 = Slider(min=10, max=600, value=360)
   l3 = Label(text='cx: {}'.format(int(self.sl3.value)))
   slb2.add_widget(self.sl3)
   slb2.add_widget(l3)
   self.sl4 = Slider(min=10, max=300, value=50)
   l4 = Label(text='cy: {}'.format(int(self.sl4.value)))
   slb2.add_widget(self.sl4)
   slb2.add_widget(l4)
   sliders_xy.add_widget(slb2)
   
   lo.add_widget(sliders_wh)
   lo.add_widget(sliders_xy)
   
   self.flo = FloatLayout() # circle canvas
   
   lo.add_widget(self.flo)
   
   # redraw cicle
   self.ev = Clock.schedule_interval(self.callback, .3)
   return lo

但是,为了不断刷新画布上圆形的外观和位置,我们需要安排一个周期性事件,清除画布以擦除现有的圆形,并使用宽度、高度、cx和cy的瞬时值重新绘制它。

以下回调方法必须添加到App类中:

def callback(self, dt):
   self.flo.canvas.clear()
   with self.flo.canvas:
      Color(1, 1, 1)
      Ellipse(
         pos=(self.sl3.value, self.sl4.value),
         size=(self.sl1.value, self.sl2.value),
         angle_start=0, angle_end=360
      )

您现在可以运行代码了。将获得与“kv”文件版本完全相同的结果。

Kivy - 控件动画

Kivy工具包中的任何部件都可以进行动画处理。您需要做的就是定义一个Animation类的对象,选择目标部件的至少一个属性进行动画处理,并指定动画效果完成后要达到的最终值。调用Animation对象的start()方法,并将目标部件传递给它。

anim = Animation(property1=value1, property2=value2, ..)
anim.start(widget)

在以下示例中,我们放置了四个Kivy按钮。两个按钮沿X轴放置,保持“y”坐标为0并随机化“x”坐标,以便一个按钮放置在前半部分,另一个放置在后半部分。

类似地,另外两个按钮沿Y轴放置,它们的“x”坐标为0,“y”坐标值随机分配。

沿X轴放置的按钮被设置为上下移动动画。“y”坐标值从其初始值一直到窗口的最大高度,然后返回到原始位置。由于repeat属性设置为True,所以上下移动是循环的。两个水平放置的按钮都绑定到以下方法:

def animate1(self, instance):
   animation = Animation(pos=(instance.pos[0], Window.height))
   animation += Animation(pos=(instance.pos[0], 0))
   animation.repeat=True
   animation.start(instance)

类似地,垂直排列的按钮b3和b4绑定到以下方法。它们的“x”坐标值从当前值更改为最大宽度,然后返回。

def animate2(self, instance):
   animation = Animation(pos=(Window.width, instance.pos[1]))
   animation += Animation(pos=(0, instance.pos[1]))
   animation.repeat=True
   animation.start(instance)

虽然可以通过按下每个按钮来开始每个按钮的动画,但我们可以在触摸按下事件上同时启动所有四个按钮的动画。上述回调由trigger_action()方法触发。

def on_touch_down(self, *args):
   self.b1.trigger_action(5)
   self.b2.trigger_action(10)
   self.b3.trigger_action(15)
   self.b4.trigger_action(20)

其余代码只是在App类的build()方法中设置四个按钮的UI。

示例

以下是完整的代码 -

import kivy
kivy.require('1.0.7')
import random
from kivy.animation import Animation
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.floatlayout import FloatLayout
from kivy.core.window import Window

Window.size = (720,400)

class TestApp(App):
   def animate1(self, instance):
      animation = Animation(pos=(instance.pos[0], Window.height))
      animation += Animation(pos=(instance.pos[0], 0))
      animation.repeat=True
      animation.start(instance)

   def animate2(self, instance):
      animation = Animation(pos=(Window.width, instance.pos[1]))
      animation += Animation(pos=(0, instance.pos[1]))
      animation.repeat=True
      animation.start(instance)
      
   def on_touch_down(self, *args):
      self.b1.trigger_action(5)
      self.b2.trigger_action(10)
      self.b3.trigger_action(15)
      self.b4.trigger_action(20)
      
   def build(self):
      box=FloatLayout()
      # create a button and attach animate() method
      # as a on_press handler
      self.b1 = Button(
         size_hint=(.15, .08), text='BTN1',
         pos=(random.randint(Window.width/2, Window.width), 0),
         on_press=self.animate1
      )
      self.b2 = Button(
         size_hint=(.15, .08), text='BTN2',
         pos=(random.randint(0, Window.width/2), 0),
         on_press=self.animate1
      )
      self.b3 = Button(
         size_hint=(.15, .08), text='BTN3',
         pos=(0, random.randint(0, Window.height/2)),
         on_press=self.animate2
      )
      self.b4 = Button(
         size_hint=(.15, .08), text='BTN4',
         pos=(0, random.randint(Window.height/2, Window.height)),
         on_press=self.animate2
      )
      
      box.add_widget(self.b1)
      box.add_widget(self.b2)
      box.add_widget(self.b3)
      box.add_widget(self.b4)
      
      box.bind(on_touch_down=self.on_touch_down)
      return box
      
if __name__ == '__main__':
   TestApp().run()

输出

程序以随机的按钮位置开始。点击应用程序窗口上的任意位置。按钮b1和b2将开始上下移动。按钮b3和b4将开始来回移动。

这是初始位置:

Kivy Widget Animation

下图是按钮在移动过程中的屏幕截图:

Kivy Widget Animation Button

Kivy - 杂项

Kivy - 异常处理

在编程中,异常处理指的是防止程序在遇到运行时错误时崩溃的机制。Kivy提供了一个名为ExceptionHandler的类,可以管理由Kivy或您自己的代码引发的异常。

ExceptionManager类在“kivy.base”模块中定义。您需要从“kivy.base”中导入它并访问处理Kivy异常的实例。您可以使用此类为不同类型的异常添加自定义处理程序,或在发生异常时覆盖Kivy的默认行为。例如,您可以使用handle_exception方法记录异常、向用户显示消息或优雅地退出应用程序。

from kivy.base import ExceptionHandler, ExceptionManager
from logging import Logger

class handler(ExceptionHandler):
   def handle_exception(self, inst):
      Logger.exception('Exception caught by ExceptionHandler')
      return ExceptionManager.PASS
      
ExceptionManager.add_handler(handler())

一个处理程序函数,它将异常作为参数并返回以下值之一:

  • ExceptionManager.PASS - 异常应被忽略,因为它已由处理程序处理。

  • ExceptionManager.RAISE - 异常应重新引发。

  • ExceptionManager.USER_HANDLED - 异常已由用户处理,不应记录。

您还可以使用handle_exception方法使用已注册的处理程序手动处理异常。

Kivy - 资源管理

“kivy.resources”模块包含用于在路径列表中搜索特定资源的功能,特别是当您的应用程序处理多个路径和项目时。

当Kivy查找任何资源(例如图像文件或“kv”文件)时,它会在预先确定的一组文件夹中搜索。您可以使用resource_add_path()和resource_remove_path()函数修改此文件夹列表。

如果您想使用defaulttheme0.png或style.kv的任何替代方案,您可以通过resource_add_path()方法将路径添加到您首选的替代方案。

以下函数在“kivy.resources”模块中定义:

  • resource_add_path(path) - 添加要搜索的自定义路径。

  • resource_find(filename, use_cache=False) - 在路径列表中搜索资源。查找结果会缓存60秒。可以使用use_cache=False禁用此功能。

  • resource_remove_path(path) - 删除搜索路径。

Kivy - 弱代理

Python使用引用计数算法进行垃圾回收,通过计算有多少对象引用某个对象。如果垃圾回收器发现某个对象被另一个对象引用,则无法对其进行垃圾回收。如果计数器达到零,垃圾回收器将释放该对象。

弱引用是不保护对象免于被垃圾回收的引用。为了创建弱引用,Python为我们提供了一个名为weakref的模块。

Kivy在kivy.weakproxy模块中定义了WeakProxy类。为了允许垃圾回收,WeakProxy类提供对对象的弱引用。它通过添加比较支持有效地增强了weakref.proxy。

Kivy - 上下文

Kivy对象Clock、Cache和Builder是全局对象。要在当前应用程序的上下文中使用它们,您必须注册它。kivy.context模块定义了一个Context类,它继承了Python内置的dict类的属性。

除了dict方法外,我们还在此模块中定义了以下函数:

  • get_current_context() - 返回当前上下文。

  • register_context(name, cls, *args, **kwargs) - 注册新的上下文。

广告