💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 编程向导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部件