输入管理

Kivy 能够处理大多数类型的输入:鼠标、触摸屏、加速度计、陀螺仪等。它处理以下平台上的原生多点触控协议:Tuio、WM_Touch、MacMultitouchSupport、MT Protocol A/B 和 Android。

全局架构可以看作:

Input providers -> Motion event -> Post processing -> Dispatch to Window

所有输入事件的类是 MotionEvent. 它产生两种事件:

  • 触摸事件:至少包含 X 和 Y 位置的运动事件。所有触摸事件都在 Widget 树中分发。

  • 非接触事件:其余所有。例如,加速度计是一个连续事件,没有位置。它从不开始或停止。这些事件不会跨 Widget 树分派。

运动事件由. Input Provider 负责从操作系统、网络甚至另一个应用程序读取输入事件。存在多个输入提供程序,例如:Input Provider

  • TuioMotionEventProvider: 创建一个 UDP 服务器并监听 TUIO/OSC 消息。

  • WM_MotionEventProvider: 使用 windows API 读取多点触控信息并将其发送到 Kivy。

  • ProbeSysfsHardwareProbe:在 Linux 中,遍历连接到计算机的所有硬件,并为找到的每个多点触控设备附加一个多点触控输入提供程序。

  • 以及更多!

编写应用程序时,不需要创建输入提供程序。Kivy 尝试自动检测可用的硬件。然而,如果你想支持自定义硬件,你将需要配置 kivy 以使其工作。

在将新创建的 Motion Event 传递给用户之前,Kivy 会对输入进行后处理。每个运动事件都会被分析以检测和纠正错误输入,并做出有意义的解释,例如:

  • 根据距离和时间阈值进行双击/三次敲击检测

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

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

处理后,运动事件被分派到窗口。如前所述,并非所有事件都被分派到整个小部件树:窗口会过滤它们。对于给定的事件:

运动事件配置文件

根据您的硬件和使用的输入提供程序,可能会向您提供更多信息。例如,触摸输入有一个 (x,y) 位置,但也可能有压力信息、斑点大小、加速度矢量等。

配置文件是一个字符串,指示运动事件中可用的功能。假设您在一个on_touch_move方法中:

def on_touch_move(self, touch):
    print(touch.profile)
    return super(..., self).on_touch_move(touch)

打印可以输出:

['pos', 'angle']

警告

许多人混淆了配置文件的名称和相应属性的名称。只是因为'angle'在可用配置文件中并不意味着触摸事件对象将具有属性angle

对于'pos'配置文件,属性posxy将可用。使用'angle'配置文件,该属性a将可用。正如我们所说,对于触摸事件'pos'是一个强制配置文件,但不是 'angle'。您可以通过检查配置文件是否存在来扩展您的交互'angle'

def on_touch_move(self, touch):
    print('The touch is at position', touch.pos)
    if 'angle' in touch.profile:
        print('The touch angle is', touch.a)

您可以在文档中找到可用配置文件的列表 motionevent

触摸事件

触摸事件是一种特殊事件MotionEvent ,其中属性的is_touch 计算结果为 True。对于所有触摸事件,您会自动获得可用的 X 和 Y 位置,并缩放到窗口宽度和高度。换句话说,所有的触摸事件都有'pos'配置文件。

触摸事件基础

默认情况下,触摸事件会分派给所有当前显示的小部件。这意味着小部件接收触摸事件,无论它是否发生在它们的物理区域内。

如果您有使用其他 GUI 工具包的经验,这可能有违直觉。这些通常将屏幕划分为几何区域,并且仅在坐标位于小部件区域内时才将触摸或鼠标事件分派给小部件。

在使用触摸输入时,此要求变得非常严格。轻扫、捏合和长按很可能来自想要了解它们并对它们做出反应的小部件外部。

为了提供最大的灵活性,Kivy 将事件分派给所有的小部件,并让它们决定如何对它们做出反应。如果您只想响应小部件内的触摸事件,您只需检查:

def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):
        # The touch has occurred inside the widgets area. Do stuff!
        pass

坐标

一旦使用带有矩阵变换的小部件,就必须在触摸时注意矩阵变换。一些小部件有 Scatter自己的矩阵变换,这意味着触摸必须乘以散点矩阵才能正确地将触摸位置分派给 Scatter 的子元素。

  • 获取从父空间到局部空间的坐标: to_local()

  • 获取从局部空间到父空间的坐标: to_parent()

  • 获取从本地空间到窗口空间的坐标: to_window()

  • 获取从窗口空间到局部空间的坐标: to_widget()

您必须使用其中之一来将坐标正确地缩放到上下文。让我们看看分散实现:

def on_touch_down(self, touch):
    # push the current coordinate, to be able to restore it later
    touch.push()

    # transform the touch coordinate to local space
    touch.apply_transform_2d(self.to_local)

    # dispatch the touch as usual to children
    # the coordinate in the touch is now in local space
    ret = super(..., self).on_touch_down(touch)

    # whatever the result, don't forget to pop your transformation
    # after the call, so the coordinate will be back in parent space
    touch.pop()

    # return the result (depending what you want.)
    return ret

触摸形状

如果触摸有形状,它将反映在“形状”属性中。现在,只有一个ShapeRect可以暴露:

from kivy.input.shape import ShapeRect

def on_touch_move(self, touch):
    if isinstance(touch.shape, ShapeRect):
        print('My touch have a rectangle shape of size',
            (touch.shape.width, touch.shape.height))
    # ...

双击

双击是在一定时间和一定距离内敲击两次的动作。它由doubletap 后处理模块计算得出。您可以测试当前触摸是否是双击之一:

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)
    # ...

三击

三重敲击是在一定时间内和一定距离内敲击三次的动作。它由 tripletap 后处理模块计算得出。您可以测试当前触摸是否是三次点击之一:

def on_touch_down(self, touch):
    if touch.is_triple_tap:
        print('Touch is a triple tap !')
        print(' - interval is', touch.triple_tap_time)
        print(' - distance between previous is', touch.triple_tap_distance)
    # ...

抓取触摸事件

父小部件可以从 内部on_touch_down而不是on_touch_move或 将触摸事件分派给子小部件on_touch_up。这在某些情况下可能会发生,例如当触摸运动在父级的边界框之外时,因此父级决定不通知其子级移动。

但是你可能想在on_touch_up. 假设您在事件中开始了一些事情on_touch_down,例如播放声音,并且您想要在on_touch_up事件中完成一些事情。抓住是你需要的。

当您抓住触摸时,您将始终收到移动和向上事件。但是抓取有一些限制:

  • 您将至少收到两次事件:一次来自您的父母(正常事件),另一次来自窗口(抓取)。

  • 您可能会收到带有抓取触摸的事件,但不是来自您:这可能是因为父级在处于抓取状态时已将触摸发送给其子级。

以下是如何使用抓取的示例:

def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):

        # if the touch collides with our widget, let's grab it
        touch.grab(self)

        # and accept the touch.
        return True

def on_touch_up(self, touch):
    # here, you don't check if the touch collides or things like that.
    # you just need to check if it's a grabbed touch event
    if touch.grab_current is self:

        # ok, the current touch is dispatched for us.
        # do something interesting here
        print('Hello world!')

        # don't forget to ungrab ourself, or you might have side effects
        touch.ungrab(self)

        # and accept the last up
        return True

触摸事件管理

为了了解触摸事件是如何在小部件之间控制和传播的,请参阅 小部件触摸事件冒泡部分。

操纵杆事件

操纵杆输入表示通过 SDL2 提供程序通过以下事件直接从物理或虚拟控制器接收的原始值:

  • SDL_悦轴运动

  • SDL_乐和动

  • SDL_JOYBALL运动

  • SDL_JOYBUTTONDOWN

  • SDL_JOYBUTTONUP

每个运动事件都有一个最小值、最大值和默认值,可以达到:

事件

最低限度

最大限度

默认

在_joy_axis

-32767

32767

0

on_joy_hat

(-1, -1)

(1, 1)

(0, 0)

欢乐球

未知

未知

未知

另一方面,按钮事件基本上只代表每个按钮的状态,即updown,因此不存在这样的值。

  • on_joy_button_up

  • on_joy_button_down

操纵杆事件基础

与触摸事件不同,操纵杆事件直接发送到窗口,这意味着只有一个值传递给指定的轴,而不是多个值。如果您想将输入分离到不同的小部件,这会使事情变得更难,但并非不可能。您可以使用多个 dropfile 示例作为灵感。

要获得操纵杆事件,您首先需要将一些函数绑定到 Window 操纵杆事件,如下所示:

Window.bind(on_joy_axis=self.on_joy_axis)

然后你需要为你使用的每个事件获取指定的参数 Window,例如:

def on_joy_axis(self, win, stickid, axisid, value):
    print(win, stickid, axisid, value)

变量stickid是发送值的控制器的 id,axisid是值所属轴的 id。

操纵杆输入

Kivy 应该能够从指定为gamepadjoystick或 SDL2 供应商认可的任何其他类型的游戏控制器的任何设备获取输入。为了使事情更容易,这里有一些常见控制器的布局以及每个部分的 ID。

Xbox 360

#

ID

#

ID

1个

轴 1

2个

轴 0

3个

帽子 Y

4个

帽子 X

5个

轴 4

6个

轴 3

7

轴 2

8个

轴 5

9

按钮 4

10

按钮 5

X

按钮 2

按钮 3

A

按钮 0

按钮 1

后退

按钮 6

开始

按钮 7

中心

按钮 10

操纵杆调试

大多数情况下,您希望使用多个控制器调试您的应用程序,或者针对_其他_类型的控制器(例如不同品牌)对其进行测试。作为替代方案,您可能希望使用一些可用的控制器模拟器,例如vJoy

Last updated