### 編程向導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部件