输入管理

输入架构

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

全局架构可以视为::

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

所有输入事件的类为 MotionEvent。它生成两种类型的事件:

  • 触摸事件:一种包含至少X和Y坐标的运动事件。所有触摸事件都会在Widget树中分发。

  • 无触摸事件:其余所有事件。例如,加速度计是一个连续事件,没有位置信息。它从不开始或停止。这些事件不会在Widget树中分发。

一个运动事件由 输入提供者 生成。输入提供者负责从操作系统、网络甚至其他应用程序读取输入事件。存在多种输入提供者,例如:

  • 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)

print 可以输出::

['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

触摸形状

如果触摸具有形状,它将会在“shape”属性中体现。目前,仅支持暴露:class:`~kivy.input.shape.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))
    # ...

双击

双击是指在时间和距离范围内连续点击两次的动作。它由双击后处理模块计算得出。你可以测试当前触摸是否为双击之一,如下所示:

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 事件中结束它。这时,抓取(Grabbing)就是你所需要的。

当你抓取一个触摸事件时,你总会收到移动和抬起事件。但抓取存在一些限制:

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

  • 你可能会收到一个带有抓取触摸的事件,但并非来自你:这可能是因为父级在抓取状态下将触摸发送给了其子级。

以下是使用grabbing的一个示例:

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

触摸事件管理

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

摇杆事件

操纵杆输入表示通过SDL3提供程序经由这些事件直接从物理或虚拟控制器接收到的原始值:

  • SDL_JOYAXISMOTION

  • SDL_JOYHATMOTION

  • SDL_JOYBALLMOTION

  • SDL_JOYBUTTONDOWN

  • SDL_JOYBUTTONUP

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

事件

最小。

最大

默认

on_joy_axis

-32767

32767

好的,请发送需要翻译的英文内容。

on_joy_hat

(-1, -1)

(1, 1)

(0, 0)

on_joy_ball

未知。

未知。

未知。

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

  • on_joy_button_up

  • on_joy_button_down

摇杆事件基础

与触摸事件不同,摇杆事件直接分派到窗口,这意味着例如指定轴只传递一个值,而非多个值。这使得将输入分离到不同控件变得困难,但并非不可能。你可以参考 Multiple dropfile example 作为灵感。

要获取摇杆事件,首先需要将某个函数绑定到Window的摇杆事件上,如下所示:

Window.bind(on_joy_axis=self.on_joy_axis)

然后,对于你使用的每个事件,你需要从:class:`~kivy.core.window.Window`中获取指定的参数,例如:

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

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

操纵杆输入

Kivy 应能从任何被指定为 gamepadjoystick 或基本上任何其他由 SDL3 提供程序识别的游戏控制器设备获取输入。为方便起见,这里列出了一些常见控制器的布局,以及每个部件的 ID。

Xbox 360

我注意到您发送的内容是“xbox_ctr”,这看起来像是一个变量名或标识符,而不是需要翻译的英文技术文档内容。请提供需要翻译的英文文本,我将为您翻译成简体中文。

好的,请发送需要翻译的英文内容。

ID

好的,请发送需要翻译的英文内容。

ID

好的,请发送需要翻译的英文内容。

轴 1

好的,请发送您需要翻译的英文内容。

轴 0

好的,请发送需要翻译的英文内容。

Y

好的,请发送您需要翻译的英文内容。

X

5

轴 4

好的,请发送需要翻译的英文内容。

轴 3

好的,请发送需要翻译的英文内容。

轴 2

8

轴 5

9

按钮4

10

按钮 5

X

按钮 2

Y

按钮 3

好的,请发送需要翻译的英文内容。

按钮 0

好的,请发送需要翻译的英文内容。

按钮1

好的,请发送需要翻译的英文内容。

按钮 6

好的,请发送需要翻译的英文内容。

按钮 7

居中

按钮 10

操纵杆调试

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