標籤:

Kivy中文編程指南:輸入管理

原文地址

譯者前言

這一章節比上一章節翻譯的還差,最近睡眠不太好,術後恢複比較差,大家湊合看看,看不下去給指出來一下比較不好理解和繞的地方,以及錯誤的地方,我一定即時修改。

輸入體系

Kivy能處理絕大多數的輸入類型:滑鼠,觸摸屏,加速器,陀螺儀等等。並且針對以下平台能夠處理多點觸控的原生協議:Tuio, WM_Touch, MacMultitouchSupport, MT Protocol A/B 以及 Android。(譯者註:第一個TUIO應該是通用多點觸控,第二個懷疑是WindowsMobile的,第三個是蘋果的多點觸控,第四個不知道是啥,最後一個是Android的。)

整體上輸入體系的結構概括起來如下所示:

Input providers -> Motion event -> Post processing -> Dispatch to Windownn輸入源 -> 動作事件 -> 事後處理 -> 分派到窗口n

所有輸入事件的類是MotionEvent。這個類生成兩種事件:

  • Touch觸控事件:包含位置信息,至少X和Y坐標位置的一種Motion動作事件。所有這種Touch事件都通過控制項樹進行分派。

  • Non-Touch非觸控事件:其餘的各種事件。例如加速度感測器就是一個持續的事件,不具有坐標位置。這一事件沒有起止,一直在發生。這類的事件都不通過控制項樹來分派。

Motion動作事件是由InputProvider生成的。 InputProvider這個類就是負責讀取輸入事件,這些輸入事件的來源可以是操作系統,網路或者其他的應用程序。如下這幾個都是已有的輸入源:

  • TuioMotionEventProvider:創建一個UDP服務端,偵聽TUIO/OSC信息。
  • WM_MotionEventProvider:使用Windows API來讀取多點觸控信息並發送給Kivy。
  • ProbeSysfsHardwareProbe:在Linux中,遍歷連接到計算機的所有硬體,並為找到的每個多點觸摸設備附加一個多點觸摸輸入提供程序。
  • 還有很多很多啦!

當你寫一個應用程序的時候,就不用再去重造一個輸入源了。Kivy會自動檢測可用的硬體。然而,如果你想要支持某些特殊定製的專門硬體,就可能得對Kivy的配置進行一下調整才行。

在新建的Motion動作事件被傳遞給用戶之前,Kivy會先對輸入進行處理。Kivy會對每一個動作事件進行分析來檢查和糾正錯誤輸入,也是保證能提供有意義的解釋,比如:

  • 根據姿勢和持續時間來檢測雙擊或三次點擊;
  • 在硬體設備精度不佳的情況下提高事件精確度;
  • 原生觸摸硬體若在近似相同位置發送事件則降低生成事件數量。

經過上面這些步驟之後,這個Motion動作事件就會被分派給對應的窗口。正如之前解釋過的,並非所有事件都分派給整個控制項樹,程序窗口要對事件進行過濾篩選。對於一個給定的事件:

  • 如果僅僅是一個Motion動作事件,那它就會被分派給on_motion();

  • 如果是一個Touch事件,這個觸摸控制項的坐標位置(x,y)(範圍在0-1)會被調整到與窗口尺寸(寬高)相適應,然後對應發給下面這些方法:

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

Motion動作事件的屬性

你用的硬體和輸入源可能允許你能獲取到更多信息。比如一個Touch觸摸輸入不僅有坐標位置(x,y),還可能有壓力強度信息,觸摸範圍大小,加速度矢量等等。

在Motion動作事件中,有一個字元串作為profile屬性,用於說明該事件內都有那些可用的效果。假如咱們有下面這樣的一個on_touch_move方法:

def on_touch_move(self, touch):n print(touch.profile)n return super(..., self).on_touch_move(touch)n

在控制台的列印輸出可能是:

[pos, angle]n

特別注意

很多人可能會把這裡Motion事件的Profile屬性的名字與對應的Property屬性弄混。一定要注意,可用Profile屬性中存在angle,並不意味著Touch事件對象也必須有一個angle的Property屬性。

對應profile屬性pos,property屬性中有位置信息pos,x,y。profile屬性angle,property屬性對應的是有角度a。剛剛我們就說了,對touchTouch事件來說,profile屬性中按照慣例是必須有位置屬性pos的,但不一定有角度屬性angle。對角度屬性angle是否存在,可以用下面的方法來檢測一下:

def on_touch_move(self, touch):n print(The touch is at position, touch.pos)n if angle in touch.profile:n print(The touch angle is, touch.a)n

在motionevent文檔中,可以找到所有可用profile屬性的列表。

Touch事件

有一種特殊的MotionEvent動作事件 ,這種事件的is_touch 方法返回的是True,這就是Touch事件。

所有的Touch事件,都默認就有X和Y的坐標信息,與窗口的寬度和高度相匹配。換句話說就是所有的Touch事件都有pos這一profile屬性。

基本簡介

默認情況下,Touch事件會被分派給所有當前顯示的控制項。也就是說無論這個Touch是否發生在控制項的物理範圍內,控制項都會收到它。

如果你接觸過其他的GUI框架,可能覺得這特點挺違背直覺的。一般的GUI框架裡面,都是把屏幕分割成多個幾何區域,然後只在發生區域內的控制項才會被分派到觸摸或者滑鼠事件。

這個設定對觸摸輸入的情景來說就過於嚴格了。因為用手指劃,之間點戳,還有長時間按,都可能會有偏移導致落到 用戶希望進行交互的控制項外的情景。

為了提供最大的靈活性,Kivy會把事件分派給所有控制項,然後讓控制項來自行決定如何應對這些事件。如果你只希望在某個控制項內對Touch事件作出反應,只需要按照如下方法進行一下檢測:

def on_touch_down(self, touch):n if self.collide_point(*touch.pos):n # The touch has occurred inside the widgets area. Do stuff!n passn

坐標位置

一旦你使用一個帶有矩陣變換的控制項,就一定要處理好Touch事件中的矩陣變換。例如Scatter這樣的某些控制項,自身會有矩陣變換,這就意味著Touch事件也必須用Scatter矩陣進行處理,這樣才能正確地把Touch事件的位置分派給Scatter的子控制項。

  • 從上層空間到本地空間獲取坐標: to_local()
  • 從本地空間到上層空間獲取坐標: to_parent()
  • 從本地空間到窗口空間獲取坐標: to_window()
  • 從窗口空間到本地空間獲取坐標: to_widget()

一定要使用上面方法當中的某一種來確保內容坐標系適配正確。然後下面這段代碼里是Scatter的實現:

def on_touch_down(self, touch):n # push the current coordinate, to be able to restore it latern # 這裡用push先把當前的坐標位置存留起來,以後就還可以恢復到這個坐標n touch.push()nn # transform the touch coordinate to local spacen # 接下來就是把Touch的坐標轉換成本地空間的坐標n touch.apply_transform_2d(self.to_local)nn # dispatch the touch as usual to childrenn # the coordinate in the touch is now in local spacen # 轉換之後把這個Touch事件按照慣例分派給子控制項n # Touch事件的坐標位置現在就是本地空間的了n ret = super(..., self).on_touch_down(touch)nn # whatever the result, dont forget to pop your transformationn # after the call, so the coordinate will be back in parent spacen #無論結果如何,一定記得把這個轉換用pop彈出n # 之後,坐標就又恢復成上層空間的了n touch.pop()nn # return the result (depending what you want.)n # 最後就是返回結果了n return retn

Touch事件的形狀

If the touch has a shape, it will be reflected in the 『shape』 property. Right now, only a ShapeRect can be exposed:

如果你的Touch事件有某個形狀,這個信息會反映在shape這一property屬性中。目前能用的就是一個 ShapeRect:

from kivy.input.shape import ShapeRectndef on_touch_move(self, touch):n if isinstance(touch.shape, ShapeRect):n print(My touch have a rectangle shape of size,n (touch.shape.width, touch.shape.height))n # ...n

雙擊

A double tap is the action of tapping twice within a time and a distance. It』s calculated by the doubletap post-processing module. You can test if the current touch is one of a double tap or not:

雙擊是一種特定動作,在一小段時間和很短的一小段特定距離內敲擊兩下。雙擊的計算識別是通過一個雙擊後處理模塊來實現的。可以用如下代碼來檢測當前的Touch是否是雙擊動作中的一下:

def on_touch_down(self, touch):n if touch.is_double_tap:n print(Touch is a double tap !)n print( - interval is, touch.double_tap_time)n print( - distance between previous is, touch.double_tap_distance)n # ...n

三次點擊

A triple tap is the action of tapping thrice within a time and a distance. It』s calculated by the tripletap post-processing module. You can test if the current touch is one of a triple tap or not:

三次點擊和雙擊的概念類似,只不過是變成了點擊三次。這個是通過一個三次點擊後處理模塊來計算識別的。可以用如下代碼來檢測當前的Touch是否是三次點擊動作中的一下:

def on_touch_down(self, touch):n if touch.is_triple_tap:n print(Touch is a triple tap !)n print( - interval is, touch.triple_tap_time)n print( - distance between previous is, touch.triple_tap_distance)n # ...n

拖放事件

父控制項可能會從on_touch_down中分派Touch事件到子控制項,而不從on_touch_move或on_touch_up分派。這可能發生在某些特定情況知悉啊,比如一個Touch處於父控制項的邊界之外,這樣父控制項就會決定不對子控制項通知這個Touch。

But you might want to do something in on_touch_up. Say you started something in the on_touch_down event, like playing a sound, and you』d like to finish things on the on_touch_up event. Grabbing is what you need.

不過有可能你還是得處理一下on_touch_up。比方說,你開始是on_touch_down事件,假設是按下播放語音之類的,然後你希望當手指抬起的時候on_touch_up事件發生的時候就結束任務。這時候就需要有Grab拖放事件了。

When you grab a touch, you will always receive the move and up event. But there are some limitations to grabbing:

拖放一個Touch的時候,總會收到移動和抬起事件。但對拖放有如下的限制:

  • 至少會兩次收到這個事件:一次是從父控制項正常收到的事件,還有一次是從窗口獲取的Grab拖放事件。

  • 有可能你沒有進行拖放,但還是會收到一個拖放Touch事件:這可能是因為在子控制項處於拖放狀態時,父控制項發來了一個Touch事件。

  • 在拖放狀態下,Touch事件的坐標不會轉換成控制項空間的坐標,因為這個Touch事件是直接來自窗口的。所以要手動將坐標轉換到本地空間。

下面這段代碼展示了對拖放的使用:

def on_touch_down(self, touch):n if self.collide_point(*touch.pos):nn # if the touch collides with our widget, lets grab itn touch.grab(self)nn # and accept the touch.n return Truenndef on_touch_up(self, touch):n # here, you dont check if the touch collides or things like that.n # you just need to check if its a grabbed touch eventn if touch.grab_current is self:nn # ok, the current touch is dispatched for us.n # do something interesting heren print(Hello world!)nn # dont forget to ungrab ourself, or you might have side effectsn touch.ungrab(self)nn # and accept the last upn return Truen

Touch事件管理

想要了解更多Touch事件如何控制以及如何在控制項之間傳遞,可以閱讀一下Widget touch event bubbling這部分內容。

推薦閱讀:

python安裝pandas包快要崩潰了?
在windows7中python3.4下如何升級pip?
使用Python定製詞雲
Python數據挖掘實踐—決策樹
Python 字母行轉序號應該怎麼做?

TAG:Python | Kivy |