四、编程向导(4.6输入管理)

编程向导4.6输入管理

一、输入架构

Kivy能处理很多类型的输入:鼠标、触摸屏、加速器、陀螺仪等等。它在下列的平台中能处理多点触摸协议:Tuio,WM_Touch, MacMultitouchSupport, MT Protocol A/B and Android.

输入全局架构可以被描述为:

输入提供者(Input Providers) -> 运动事件(Motion event) -> 投递处理(Post processing) -> 发送到窗口(Dispatch to Window)

所有管理输入事件的类是运动事件(MotionEvent)。它派生成两种类型的事件类:

  • 触摸事件:一个运动事件能至少包含X和Y坐标。所有的触摸事件通过部件树进行发送。

  • 非触摸事件:剩下的,例如加速器是一个连续事件,没有坐标。它没有开始和停止状态。这些事件不通过部件树发送。

一个运动事件被一个输入提供者(Input Provider)生成。一个输入提供者负责从操作系统、网络或其他应用程序读取输入事件。下是几个输入提供者:

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

  • WM_MotionEventProvider:使用窗口API读取多点触摸信息并发送到Kivy。

  • ProbeSysfsHardwareProbe:在Linux系统下,迭代所有连接到计算机上的硬件,并附着一个多点输入提供者为每一个被发现的多点触摸设备。

  • 更多:...
    当你写一个应用程序时,你不必创建一个输入提供者,Kivy会尝试自动检测可用的硬件。但是,如果你想支持定制的硬件,你就需要配置Kivy并使它工作。

在新创建的运动事件被传递到用户之前,Kivy应用投递处理(post-processing)到输入。每一个运动事件被分析并纠正错误输入,做出有意义的解释:

  • 通过距离和时间临界值,检测双击/三击(Double/triple-tap detection)。
  • 当硬件不是太精确时,使得事件更精确。
  • 如果本地触摸硬件在基本相同的位置发送多个事件时,降低事件的数量。

当处理事件后,运动事件被发送到窗口。正如前面解释的那样,不是所有的事件都发送到事件树:窗口过滤它们。对于一个事件:

  • 如果它仅仅是一个运动事件,它会被发送到on_motion()

  • 如果它是一个触摸事件,则触摸点的(x, y)坐标(在0-1范围内)会被重新转换为屏幕尺寸(宽/高),并发送到:

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

二、运动事件配置

依赖你的硬件和使用的输入提供者,也许有更多的信息可以被使用。例如,触摸输入有(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'配置,属性pox, x, y是可用的。对于'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文档中找到一个关于可用的配置的列表。

三、触摸事件

一个触摸事件是一个特殊的运动事件,它的属性is_touch被设置为True。对于所有的触摸事件,你会自动拥有X和Y坐标,对应着窗口的宽和高。换句话说,所有的触摸事件有'pox'配置。

(一)触摸事件基础

默认情况下,触摸事件被发送到当前所有的可显示的部件。这意味着无论事件发生在部件的内部与否,它都能收到事件。

如果你有关于其他GUI的开发经验,这可能是反直觉的。一般典型的做法是划分屏幕为几何区域,并且只有坐标在部件区域内部时才发送触摸或鼠标事件到部件。

当使用触摸事件工作时,这个要求变得非常的有限制性。强击,缩放和长按可能来自想了解部件及其如何反应的外部。

为了提供最大的灵活性,Kivy发送事件到所有的部件,并让它们决定是否响应它们。如果你仅想在部件内部响应触摸事件,你可以简单的进行检测:

def on_touch_down(self, touch):
    if self.collide_point(*touch.pos):
        #触摸发生在部件区域的内部,做自己的相应
        pass
(二)坐标

一旦你使用了一个带有矩阵转换的部件,你必须注意矩阵转换。一些部件,例如分散(Scatter)有它们自己的矩阵转换,这意味着触摸必须能正确的传递触摸坐标到Scatter的子部件。

  • 从父空间到局部空间获取坐标使用to_local()
  • 从局部空间到父空间获取坐标使用to_parent()
  • 从局部空间到window空间使用:to_window()
  • 从window空间到局部空间使用:to_widget()

你必须根据上下文使用上面的一个来转换相应的坐标。看分散(scatter)的实现:

def on_touch_down(self, touch):
    #将当前的坐标压入,为了后面能存储它
    touch.push()

    #转换触摸坐标到局部坐标
    touch.apply_transform_2d(self.to_local)

    #像平时一样,发送触摸到子部件
    #在touch里面的坐标是局部坐标
    ret = super(..., self).on_touch_down(touch)

    #无论结果如何,不要忘记调用之后弹出你的转换,
    #这样,坐标会变成父坐标
    touch.pop()

    #返回结果(依赖于你的所需)
    return ret

(三)触摸形状

如果触摸有一个形状,它可以在'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))
(四)双击

双击是指在一段时间和范围内轻点两次。它由doubletap post-processing模块来计算。你能测试当前的触摸是双击或不是:

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

三击是只在一段时间和一定范围内轻击三次。它由tripletap post-processing模块来计算。你能测试当前的触摸是否为三击:

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_moveon_touch_up就不可以。这可能发生在特定的场景下,例如当一个触摸事件发生在父部件边框外部时,父部件决定不通知它的子部件。

当你捕获了一个事件,你会总是收到移动(move)和弹起(up)事件,但是对于捕获有一些限制:

  • 你将会收到事件至少两次:一次来自你的父部件(正常事件),一次来自window(捕获)。
  • 你可能收到一个事件,但是不是来自你:它可能因为父部件发送给它的子部件。
  • 触摸坐标没有转换到你的部件空间,因为触摸是来自Window。你需要收动转换它们。

下面是一个例子来演示如何使用捕获:

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

        #如果触摸检测来自我们自己的部件,让我们捕获它。
        touch.grab(self)
        # 并响应这次触摸.
        return True

def on_touch_up(self, touch):
    #这里,你不用检测触摸碰撞或类似操作,
    #你仅需要检测是否它是一个捕获的触摸事件
    if touch.grab_current is self:
        #OK,当前触摸事件被派发给我们
        #做一些感兴趣的操作
        print('Hello world!')
        #不要忘记释放掉,否则可能会有副作用
        touch.ungrab(self)
        #最后响应这次触摸
        return True
(七)触摸事件管理

为了了解触摸事件如何在部件间被控制和传播,请参阅部件触摸事件冒泡机制(Widget touch event bubbling)

下节预告:编程向导4.7部件

你可能感兴趣的:(四、编程向导(4.6输入管理))