### 编程向导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_move**或**on_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)](https://kivy.org/docs/api-kivy.uix.widget.html#widget-event-bubbling)
### 下节预告:编程向导4.7部件