python編程(Kivy 安裝及使用教程)


Kivy是一個很優秀的,基於Python的GUI庫,可以利用Python快速編程的特點,快速的編寫windows, linux, mac, android, ios等主流平台的應用程序。同wxPython、PyQt相比,最大的優點是可以快速地編寫移動應用程序。

一, kivy 安裝

在 windows 命令行中,執行以下命令

  1.  
    python -m pip install docutils pygments pypiwin32 kivy.deps.sdl2 kivy.deps.glew
  2.  
    python -m pip install kivy.deps.gstreamer
  3.  
    python -m pip install kivy

以上如果安裝過慢,可以使用清華鏡像:pip install -i https://pypi.tuna.tsinghua.edu.cn/simple  some-package

安裝kivy官方示例

python -m pip install kivy_examples

驗證kivy安裝成功:

  1.  
    1 from kivy.app import App
  2.  
    2 from kivy.uix.button import Button
  3.  
    3
  4.  
    4 class TestApp(App):
  5.  
    5 def build(self):
  6.  
    6 return Button(text='iPaoMi')
  7.  
    7
  8.  
    8 TestApp().run()

你將看到執行上面的 Python 代碼,會運行如下的窗口,這可以算是 kivy 版的 hello world 了。

 

二,Kivy 使用:

4.1 Kivy基礎

創建一個kivy應用程序步驟:

  • 繼承App類
  • 實現它的build()方法,它能返回一個部件的實例(你的部件樹的根部件)
  • 實例化該類,同時調用它的run()方法

下面是一個最小化應用程序的例子:

  1.  
    import kivy
  2.  
    kivy.require( '1.0.6') # 用你當前的kivy版本替換
  3.  
     
  4.  
    from kivy.app import App
  5.  
    from kivy.uix.label import Label
  6.  
     
  7.  
    class MyApp(App):
  8.  
    def build(self):
  9.  
    return Label(text= 'Hello world')
  10.  
     
  11.  
    if __name__ == '__main__':
  12.  
    MyApp().run()

你可以保存上面的代碼到一個諸如main.py的text文件中,並嘗試運行它。

三、Kivy APP的生命周期

首先,讓我們熟悉一下Kivy應用程序的生命周期:

 

 

正如你上面看到的,對於所有的應用程序,我們的入口就是run()方法,在我們的例子中就是MyApp().run()。我們會面會重新回顧這里。下面我們首先看第一行代碼:

from kivy.app import App

你的應用程序的基類需要繼承APP類,它在kivy_installation_dir/kivy/app.py中。

提示:如果你想進一步的研究APP類,你可以打開該文件進行深入的探討。我們鼓勵你重讀它的代碼。Kivy是基於Python實現的並且其文檔都在實際的文件里。

第二行:

from kivy.uix.label import Label

請注意,包的路徑被展示了。uix模塊包含着例如布局、部件等用戶接口元素。

再看第五行:

  1.  
    class MyApp(App):
  2.  
     

這里定義了我們的應用程序的基類。你僅僅需要改變一下你的應用程序MyApp的名字。

第七行:

def build(self):

正如在生命周期圖片顯示的那樣,這是你初始化並返回你的根部件的地方。我們的例子在第八行:

return Label(text='Hello Kivy')

這里我們初始化了一個標簽,標簽文本是:Hello Kivy,並返回了它的實例。這個標簽將作為我們應用程序的根部件。

現在我們將在11行和12行運行我們的應用程序:

  1.  
    if __name__ == ' __main__':
  2.  
    MyApp().run()

這里,MyAPP類被實例化並運行了它的run()方法。這樣就初始化並啟動了Kivy應用程序。

四、運行應用程序

為了運行應用程序,根據你的操作系統,按照下面的說明:

  • Linux:
  1.  
    $ python main.py
  2.  
     
  • Windows:
  1.  
    $python main.py
  2.  
    或者
  3.  
    c:/appdir>kivy.bat main.py
  4.  
     
  • Mac Os X:
  1.  
    $ kivy main.py
  2.  
     
  • Android:

    你的應用程序需要一些補充的文件以便在安卓上運行。請參閱為安卓程序打包

運行程序后,一個諸如下面的窗口將被呈現:

五、定制應用程序

下面讓我們擴展一下,做一個簡單的用戶名/密碼的輸入頁面。

  1.  
    from kivy.app import App
  2.  
    from kivy.uix.gridlayout import GridLayout
  3.  
    from kivy.uix.label import Label
  4.  
    from kivy.uix.textinput import TextInput
  5.  
     
  6.  
    class LoginScreen(GridLayout):
  7.  
     
  8.  
    def __init__(self, **kwargs):
  9.  
    super(LoginScreen, self).__init__(**kwargs)
  10.  
    self.cols = 2
  11.  
    self.add_widget(Label(text='User Name'))
  12.  
    self.username = TextInput(multiline=False)
  13.  
    self.add_widget(self.username)
  14.  
    self.add_widget(Label(text='password'))
  15.  
    self.password = TextInput(password=True, multiline=False)
  16.  
    self.add_widget(self.password)
  17.  
     
  18.  
    class MyApp(App):
  19.  
     
  20.  
    def build(self):
  21.  
    return LoginScreen()
  22.  
     
  23.  
    if __name__ == '__main__':
  24.  
    MyApp().run()
  25.  
     

在第二行,我們導入了網格布局:

from kivy.uix.gridlayout import GridLayout

在第九行,這個類被用作我們根部件的作為一個基本布局:

  1.  
    class LoginScreen(GridLayout):
  2.  
     

在第12行,我們重載了方法init()以便於我們添加部件和行為:

  1.  
    def __init__(self, **kwargs):
  2.  
    super(LoginScreen, self).__init_ _(**kwargs)

我們不能忘記調用父類的super方法,以實現基類的基本功能;同時也要注意在調用super時不要忽略**kwargs參數,因為它們有時在內部使用。

第15行:

  1.  
    self.cols = 2
  2.  
    self.add_widget(Label(text= 'User Name'))
  3.  
    self.username = TextInput(multiline= False)
  4.  
    self.add_widget( self.username)
  5.  
    self.add_widget(Label(text= 'password'))
  6.  
    self.password = TextInput(password= True, multiline= False)
  7.  
    self.add_widget( self.password)

我們使用網格布局以兩列來管理它的孩子們,並且每行添加了一個標簽和一個文本輸入框。
運行程序如圖:

 

試着重新改變窗口大小,你會發現部件會自動調整尺寸。

上面的代碼沒有處理用戶的輸入,沒有使用各種數據驗證,包括部件的尺寸、位置等等。我們將會繼續深入的研究他們。

六、平台細節

打開一個終端應用程序,並且設置Kivy的環境變量

  • 在Windows平台上,僅僅需要雙擊kivy.bat,一個終端窗口會自動打開,並設置各種變量

  • 在nix* 系統下,如果kivy沒有全局安裝,打開終端輸入:

    export python=$PYTHONPATH:/path/to/kivy_installation

4.2 環境配置

許多環境變量可以用來控制Kivy的初始化和行為。
例如限制文本渲染使用PIL實現:

$ KIVY_TEXT = pil python main.py

環境變量需要在導入Kiry之前設置:

  1.  
    import os
  2.  
    os.environ[ 'KIVY_TEXT'] = 'pil'
  3.  
    import kivy

一、路徑控制

V1.0.7版本加入

你可以控制諸如配置文件、模塊、擴展和Kivy數據的默認路徑

KIVY_DATA_DIR:Kivy數據默認路徑<kivy path>/data

KIVY_EXTS_DIR:Kivy擴展默認路徑<kivy path>/extensions

KIVY_MODULES_DIR:Kivy模塊默認路徑<kivy path>/modules

KIVY_HOME:Kivy主默認路徑,該路徑被用來作為本地配置,該路徑必須是可寫入的,默認為:

  • 桌面系統:<user home\>/.kivy
    
  • 安卓系統:<android app path\>.kivy
    
  • IOS 系統:<user home\>/Documents/.kivy
    

V1.9.0版本加入

KIVY_SDL2_PATH:如果設置,在編譯Kivy時將從該路徑尋找SDL2庫和頭文件,代替系統方面的路徑。為使用相同的庫,在編譯Kivy應用程序時,路徑必須在PATH環境變量啟動前添加。

在編譯Kivy時必須使用,在執行時沒有必要。

二、配置

KIVY_USER_DEFAULTCONFIG:如果該配置被設置,則Kivy不會讀取用戶的配置文件

KIVY_NO_CONFIG:如果設置,則沒有配置文件及用戶配置路徑進行讀寫

KIVY_NO_FILELOG:如果設置,則不會記錄日志到一個文件

KIVY_NO_CONSOLELOG:如果設置,則日志不會輸出到控制台

V1.9.0版本加入

KIVY_NO_ARGS:如果設置,則在命令行傳遞的參數將不被解析,例如,你可以安全的使用你自己的不需要-分隔符的參數寫一個腳本或應用程序:

  1.  
    import os
  2.  
    os.environ[' KIVY_NO_ARGS'] = "l"
  3.  
    import kivy

三、限制核心到特定實現

kivy.core在你的系統平台中嘗試選擇最佳實現。為了測試或定制安裝,你也許想限制選擇一個特定的實現。

KIVY_WINDOW:創建一個窗口的實現,可選值為sdl2,pygame,x11, egl_rpi

KIVY_TEXT:渲染文本的實現,可選值為sdl2, pil, pygame, sdlttf

KIVY_VIDEO:渲染視頻的實現,可選值為pygst, gstplayer, pyglet, ffpyplayer, null

KIVY_AUDIO:播放音頻的實現,可選值為sdl2, gstplayer, pygst, ffpyplayer, pygame

KIVY_IMAGE:讀取圖像的實現,可選值為sdl2, pil, pygame, imageio, tex, dds, gif

KIVY_CAMERA:讀取攝像頭的實現,可選值為videocapture, avfoundation, pygst, opencv

KIVY_SPELLING:拼寫的實現,可選值為enchant, osxappkit

KIVY_CLIPBOARD:剪切板管理的實現,可選值為sdl2, pygame, dummy, android

四、度量

V1.4.0版本加入

KIVY_DPI:如果設置,則單位使用Metrics.dpi

V1.5.0版本加入

KIVY_METRICS_DENSITY:如果設置,則單位使用Metrics.density

V1.5.0加入

KIVY_METRICS_FONTSCALE:如果設置,則單位使用Metrics.fontscale

五、圖像

KIVY_GLES_LIMITS:是否GLES2限制被執行。如果設置為false,Kivy將不會真正的GLES2兼容。下面是一個當被設置為True時的潛在的不兼容列表

  • Mesh indices(網格索引):如果為True,則一個網格里面的索引被限制到65535.
  • Texture blit(材質位塊傳輸):當傳輸材質時,顏色和緩沖的數據格式必須同材質被創建時的格式相同。在桌面系統上,不同顏色間的轉換被驅動程序處理。在安卓系統上,大多數的設備會造成失敗。

V1.8.1版本加入

KIVY_BCM_DISPMANX_ID:改變默認的用來顯示Raspberry Pi,默認值為0,可選值如下:

  1.  
    0: DISPMANX_ID_MAIN_LCD
  2.  
     
  3.  
    1: DISPMANX_ID_AUX_LCD
  4.  
     
  5.  
    2: DISPMANX_ID_HDMI
  6.  
     
  7.  
    3: DISPMANX_ID_SDTV
  8.  
     
  9.  
    4: DISPMANX_ID_FORCE_LCD
  10.  
     
  11.  
    5: DISPMANX_ID_FORCE_TV
  12.  
     
  13.  
    6: DISPMANX_ID_FORCE_OTHER

4.3 配置Kivy

Kivy的配置文件命名為config.ini,遵循standard INI格式。

一、定位配置文件

配置文件的位置由環境變量KIVY_HOME來控制:

<KIVY_HOME>/config.ini

在桌面系統中,默認路徑是:

<HOME_DIRECTORY>/.kivy/config.ini

假如你的系統用戶名是'tito',那么配置文件的路徑如下:

  • Windows: c:\Users\tito\.kivy\config.ini
  • OS X : /Users/tito/.kivy/config.ini
  • Linux : /home/tito/.kivy/config.ini

在安卓系統上,默認路徑是:

<android_app_path>/.kivy/config.ini

如果你的應用程序名字為'org.kivy.launcher',文件路徑是:

/data/data/org.kivy.launcher/files/.kivy/config.ini

在IOS系統上,默認路徑為:

<HOME_DIRECTORY>/Documents/.kivy/config.ini

二、理解配置符號

所有的配置符號在kivy.config文件中均有詳細的解釋。

4.4架構預覽

我們將要花費一些時間以軟件管理的角度來解釋如何設計Kivy。這將是理解每一部分如何一起工作的關鍵。你也許瀏覽過源代碼,也許有理一個粗糙的概念;但是看源代碼也許是令人生畏的,因此這節內容將會詳細解釋一些基本的概念。你可以略過本節以待日后再看,但我們建議你至少大致瀏覽一下。

Kivy由幾個模塊組成,下面是Kivy架構圖:

 

Architectural Overview

一、核心提供者和輸入提供者

理解Kivy內部思想的關鍵是模塊化和抽象。我們試圖抽象一些基本的任務,例如打開一個窗口、展示圖片和文本、播放音頻、從攝像頭獲取圖像、拼寫檢查等等。我們稱它們為核心任務。這使得API既容易使用又容易擴展。更重要的是,它允許我們用:what we call,在你的應用程序運行時的不同場景指定不同的提供者。例如,在OS X,Linux和Windows系統上,針對不同的核心任務有不同的本地APIS。一方面這些APIS同操作系統進行交流,另一方面我們調用的Kivy的核心提供者扮演着中間交流層的角色。使用特殊核心提供者的便利之處是我們可以充分的使用操作系統暴露出來的功能,盡可能的使程序運行的更高效。它給用戶提供了一個機會。另外,通過使用那些針對一個平台的庫,我們可以高效地較低應用程序打包的尺寸。這也使得移植到別的平台更容易些。安卓端口從這方面就獲利巨大。

我們利用同樣的思路處理輸入提供者。輸入提供者,支持一些特殊的輸入設備,例如蘋果的trackpads,TUIO或者鼠標模擬器。如果你需要添加一個新的輸入設備,你只需要簡單的添加一個新的類來讀取你的輸入數據並且將其轉換到Kivy的基本事件。

二、圖形

Kivy的圖像接口是我們對OpenGL的抽象。在更低的層面,Kivy使用OpenGL來分發硬件加速指令。對於一個初學者來說,寫OpenGL代碼也許是令人迷惑的。這也是我們提供圖形API接口的原因,你只需要使用簡單的封裝好的方法(例如Canvas,Rectangle等等)進行繪畫即可。

我們的所有部件本身就是使用這些圖形接口,為了更高效的表現,它們在C語言執行。

另外一個便利之處是這些圖形接口可以自動優化繪畫指令。如果你不是一個OpenGL的專家,這將是很有用的幫助。在很多的場景下,它將使你的繪畫代碼更高效。

當然你也可以使用OpenGL命令。我們在所有的設備上使用的是OpenGL2.0(GLSE2),所以如果你想保持更好的跨平台的兼容性,我們建議你僅僅使用GLSE2提供的函數。

三、核心

核心提供者提供了一些通用的功能,例如:

  • Clock(時鍾):你可以使用時鍾來計划定時器事件。一次性的定時器和周期性的定時器都被支持。
  • Cache(緩存):如果你需要將你常用的一些東西緩存起來,你可以使用我們提供的緩存類,而不是自己再造一個輪子。
  • Gesture Detection(手勢檢測):我們提供了一個簡單的手勢識別,你可以用來檢測不同類型的軌跡,例如圓圈或者矩形。你可以自己訓練它來檢測自己的軌跡。
  • Kivy Language(Kivy語言):Kivy語言可以容易並高效地描述用戶接口。
  • Properties(屬性):這里的屬性不是你熟知的Python里面的屬性,它們是我們自己的屬性類,以用來鏈接你的部件代碼和用戶接口描述。

四、UIX(部件和布局)

UIX模塊包含常用的部件和布局,你可以重復使用它們來創建一個用戶接口。

  • Widgets:部件是用戶接口元素,它們有的可見,有的不可見,例如文件瀏覽窗口、按鈕、滑塊、列表等等。部件接收MotionEvents。
  • Layouts:你可以使用布局來排列部件。當然你可以手動的自己來布局部件,但是通常使用布局會更加的方便。布局包括網格布局、盒子布局等,你也可以布局嵌套。

五、模塊

如果你曾經使用過現代的網頁瀏覽器,並定制過一些插件,那么你已經了解了關於我們模塊類的基本概念。模塊用來被注入功能到Kivy程序中,即使原作者沒有包括它。

一個例子展示了一個總是顯示FPS的模塊和一些描繪FPS的圖表。

你也可以寫自己的模塊。

六、輸入事件(觸摸)

Kivy抽象了不同的輸入類型和資源,例如觸摸、鼠標、TUIO等等。所有這些輸入類型的共同之處是你可以使用一個屏幕上的2D坐標以及一些特殊的輸入事件關聯起來。(一些其它的輸入設備,例如加速器你就不能簡單的使用2D坐標來描述。這類輸入設備被分別對待)

所有的這些輸入設備類型被Touch()類的實例來表示。(注意這不僅僅代表手指觸摸,也同樣表示別的輸入類型。我們只是因為類似而采取了Touch的名字。)一個Touch的實例或對象,包含着三個狀態,當一個觸摸進入到了這些狀態,你的程序被通知,該事件發生了。這三個狀態如下:

  • Down:一個觸摸被按下一次
  • Move:A touch can be in this state for a potentially unlimited time. A touch does not have to be in this state during its lifetime. (一個觸摸可能在一個潛在的無限的時間里。一個觸摸在它的生命周期中不會一直在這個狀態。)當一個觸摸的2D坐標發生改變時,Move將會事件發生。

gthank:前面的兩句話不知道什么意思!

  • Up:一個觸摸抬起至多一次或沒有。實際應用中,你將總會接收到一個Up事件,因為沒有人會永遠將一個手指放在屏幕上,但是這不是有保證的。如果你知道用戶正在使用的輸入源,你將會知道是否依賴這個狀態。

七、部件和事件發送

部件這個術語通常用在GUI編程中,來描述和用戶交互的那部分程序。在Kivy中,一個部件是一個對象由來接收輸入事件。在屏幕上有一個可視的代表是沒有必要的。所有的部件在一個部件樹中管理:一個部件能有零或者多個子部件。但是在樹的頂端只能有一個根部件,根部件沒有父部件;其它的部件直接或間接的是根部件的子部件。

當一個新的輸入數據可用時,Kivy發送一個事件。根部件首先收到該事件。根據觸摸的狀態,on_touch_down, on_touch_move, on_touch_up事件被發送到根部件,這將導致在根部件中相應的事件處理函數被調用。

在樹中的每一個部件能選擇吸收或傳遞事件。如果一個事件處理函數返回True,這意味着這個事件被吸收並正確的處理,對於該事件,沒有進一步的處理會發生。否則,這個事件處理函數通過調用它的父類的各自事件處理函數的實現,傳遞部件到它自己的子部件上。最終事件傳遞到部件的基類,在它的事件處理函數里,什么到不做,僅僅傳遞touchs到它的子部件。

  1.  
    #這是一個move/up的模擬
  2.  
    def on_touch_down(self, touch):
  3.  
    for child in self.children[ :]:
  4.  
    if(child.dispatch( 'on_touch_down', touch)):
  5.  
    return True

它可能比看起來更容易。下面的章節會展示一個例子,漂亮的應用程序能夠快速的被創建。

通常你想限制一個部件監聽觸摸事件的屏幕區域。你可以使用部件的collide_point()方法來做到這點。你傳遞給它觸摸的坐標,並且它將返回監聽的區域。默認情況下,屏幕上矩形區域的檢測使用部件的坐標和尺寸來描述,但是你也能在自己的類中進行重載。

4.5 事件和屬性

事件是Kivy編程里面一個重要的部分。對於有GUI開發經驗的人來說也許不是那么讓人驚奇,但對於初學者是一個重要的概念。一旦你理解了事件如何工作、如何綁定,你將會在Kivy到處發現它們。它們使你想利用Kivy實現任何的行為變得很容易。

下面的插圖顯示了在Kivy框架中事件如何被處理。

events and properties

一、介紹事件發送

Kivy框架的最重要的基類之一就是EventDispatcher類。這個類允許你注冊事件類型並發送它們到感興趣的地方(通常是其它事件發送者)。部件動畫時鍾類都是事件發送的例子。

EventDispatcher對象依賴主循環生成和處理事件。

二、主循環

在上面插圖中,主循環作為輪廓。這個循環運行在應用程序的全部生命周期中,直到應用程序退出時才終止。

在循環里面,每一次迭代,當發生用戶輸入、傳感器或者一些其他資源、畫面被渲染顯示時,總會有事件生成。

你的應用程序可以指定回調函數,它們在主循環中被調用。如果一個回調函數費時太長或者根本不會退出,則主循環會中斷同時你的應用程序無法正常運行。

在Kivy應用程序中,你必須避免使用長循環、死循環或睡眠(sleeping),如下代碼是需要避免的:

  1.  
    while True:
  2.  
    animate_something()
  3.  
    time.sleep( .10)

當你運行上面的代碼,則你的程序永遠無法退出該循環,要預防Kivy做類似的事情。結果將會看到一個無法交互的黑色的窗口。正確的方式的,你需要定制(schedule)你的animate_somthing()函數重復調用。

(一)重復事件

你可以使用schedule_interval()每隔X時間調用一個函數或方法,下面是一個例子,每隔1/30秒調用一次my_callback函數:

  1.  
    def my_callback(dt):
  2.  
    print 'my callback is called', dt
  3.  
    Clock.schedule_interval(my_callback, 1/ 30.)

你有兩種方法來取消前面定制的事件,第一種是:

Clock.unschedule(my_callback)

或者你在你的回調函數中返回False,那么你的事件將會自動取消:

  1.  
    count = 0
  2.  
    def my_callback(dt):
  3.  
    global count
  4.  
    count += 1
  5.  
    if count == 10:
  6.  
    print 'Last call of my callback, bye bye!'
  7.  
    return False
  8.  
    print 'My callback is called'
  9.  
    Clock.schedule_interval(my_callback, 1/ 30.)

(二)單次事件

使用schedule_once(),你可以定制執行一次你的回調函數,比如在下一幀,或X秒后:

  1.  
    def my_callback(dt):
  2.  
    print 'My callback is called!'
  3.  
    Clock.schedule_once(my_callback, 1)

上面的代碼中,my_callback()函數將會在1秒后執行。1秒參數是在執行該程序前等待的時間,以秒為單位。但是你可以使用特殊的值作為時間參數得到一切其它結果:

  • 如果X > 0,則回調函數會在X秒后執行。
  • 如果X = 0, 則回調函數會在下一幀執行。
  • 如果x = -1,則回調函數在在下一幀之前執行。

其中 x = -1最為常用。

重復執行一個函數的第二種方法是:一個回調函數使用schedule_once遞歸調用了自己,在外部schedule_once函數中又調用了該回調函數:

  1.  
    def my_callback(dt):
  2.  
    print 'My callback is called !'
  3.  
    Clock.schedule_once(my_callback, 1)
  4.  
    Clock.schedule_once(my_callback, 1)

當主循環嘗試保持定制請求時,當恰好一個定制的回調函數被調用時,有一些不確定的情況會發生。有時另外一些回調函數或一些任務花費了超出預期的時間,則定時會被延遲。

在第二種解決方案中,在上一次迭代執行結束后,下一次迭代每秒至少會被調用一次。而使用schedule_interval(),回調函數則每秒都會被調用。

(三)事件跟蹤

如果你想為下一幀定制一個僅執行一次的函數,類似一個出發器,你可能這樣做:

  1.  
    Clock .unschedule(my_callback)
  2.  
    Clock .schedule_once(my_callback, 0)

這種方式的代價是昂貴的,因為你總是調用unschedule()方法,無論你是否曾經定制過它。另外,unschedule()方法需要迭代時鍾的弱引用列表,目的是找到你的回調函數並移除它。替代的方法是使用出發器:

  1.  
    trigger = Clock.create_trigger(my_callback)
  2.  
    #隨后
  3.  
    trigger()

每次你調用trigger,它會為你的回調函數定制一個信號調用,如果已經被定制,則不會重新定制。

三、部件事件

每個部件都有兩個默認的事件類型:

  • 屬性事件(Property event):如果你的部件改變了位置或尺寸,則事件被觸發。
  • 部件定義事件(Widget-defined event):當一個按鈕被按下或釋放時,事件被觸發。

四、自定義事件

為了創建一個自定義事件,你需要在一個類中注冊事件名,並創建一個同名的方法:

  1.  
    class MyEventDispatcher(EventDispatcher):
  2.  
    def __init__(self, **kwargs):
  3.  
    self.register_event_type( 'on_test')
  4.  
    super(MyEventDispatcher, self).__init_ _(**kwargs)
  5.  
     
  6.  
    def do_something(self, value):
  7.  
    #當do_something被調用時,on_test事件將會連同value被發送
  8.  
    self.dispatch( 'on_test', value)
  9.  
     
  10.  
    def on_test(self, *args):
  11.  
    print "I am dispatched", args

五、附加回調

為了使用事件,你必須綁定回調函數。當事件被發送時,你的回調函數將會連同參數被調用。

一個回調函數可以是任何python函數,但是你必須確保它接受事件發出的參數。因此,使用*args的參數會更安全,這樣將會在args列表中接收到所有的參數。例如:

  1.  
    def my_callback(value, *args):
  2.  
    print "Hello, I got an event!", args
  3.  
     
  4.  
    e = MyEventDispatcher()
  5.  
    e.bind(on_test = my_callback)
  6.  
    e.do_something( 'test')

有關附加回調函數更多的示例可以參閱kivy.event.EventDispatcher.bind()文檔

六、屬性介紹

屬性是一個很好的方法用來定義事件並綁定它們。本質上來說,當你的對象的特征值發生變化時,它們創造事件,所有的引用特征值的屬性都會自動更新。

有不同類型的屬性來描述你想要處理的數據類型。

  • StringProperty
  • NumericProperty
  • BoundedNumericProperty
  • ObjectProperty
  • DictProperty
  • ListProperty
  • OptionProperty
  • AliasProperty
  • BooleanProperty
  • ReferenceListProperty

七、聲明屬性

為了聲明屬性,你必須在類的級別進行。當你的對象被創建時,該類將會進行實際特征值的初始化。特寫屬性不是特征值,它們是基於你的特征值創建事件的機制。

  1.  
    class MyWidget(Widget):
  2.  
    text = StringProperty('')
  3.  
     

當重載init時,總是接受**kwargs參數並使用super()調用父類的init方法:

  1.  
    def __init__(self, **kwargs):
  2.  
    super(MyWidget, self).__init_ _(**kwargs)

八、發送屬性事件

Kivy的屬性,默認提供一個on_<property_name>事件。當屬性值改變時該事件被調用。

注意,如果新的屬性值等於當前值,該事件不會被調用。
例如:

  1.  
    class CustomBtn(Widget):
  2.  
    pressed = ListProperty([ 0, 0])
  3.  
     
  4.  
    def on_touch_down(self, touch):
  5.  
    if self.collide_point(*touch.pos):
  6.  
    self.pressed = touch.pos
  7.  
    return True
  8.  
    return super(CustomBtn, self).on_touch_down(touch)
  9.  
     
  10.  
    def on_pressed(self, instance, pos):
  11.  
    print( 'pressed at{pos}'.format(pos=pos))

在第3行:

pressed = ListProperty([0,0])

我們定義了pressed屬性,類型為ListProperty,默認值為[0, 0],當屬性值發生改變時,on_pressed事件被調用。

在第5行:

  1.  
    def on_touch_down(self, touch):
  2.  
    if self.collide_point(*touch.pos):
  3.  
    self.pressed = touch.pos
  4.  
    return True
  5.  
    return super(CustomBtn, self).on_touch_down(touch)

我們重載了on_touch_down()方法,我們為我們的部件做了碰撞檢測。

如果觸摸發生在我們的部件內部,我們改變touch.pos按下的值並返回True,表明我們處理了這次觸摸並不想它繼續傳遞。

最后,如果觸摸發生在我們的部件外部,我們使用super()調用原始事件並返回結果。它允許觸摸事件繼續傳遞。

最后在11行:

  1.  
    def on_pressed(self, instance, pos):
  2.  
    print ( 'pressed at {pos}'.format(pos=pos))

我們定義了on_pressed函數,當屬性值改變時,該函數被調用。

注意當屬性值被定義時,on_<prop_name>事件在類內部被調用。為了在類的外部監控或觀察任何屬性值的變動,你可以以下面的方式綁定屬性值。

your_widget_instance.bind(property_name=function_name)

例如,考慮以下代碼:

  1.  
    class RootWidget(BoxLayout):
  2.  
     
  3.  
    def __init__(self, **kwargs):
  4.  
    super(RootWidget, self).__init_ _(**kwargs)
  5.  
    self.add_widget(Button(text= 'btn 1'))
  6.  
    cb = CustomBtn()
  7.  
    cb.bind(pressed= self.btn_pressed)
  8.  
    self.add_widget(cb)
  9.  
    self.add_widget(Button(text= 'btn 2'))
  10.  
     
  11.  
    def btn_pressed(self, instance, pos):
  12.  
    print ( 'pos: printed from root widget: {pos}'.format(pos=.pos))

如果你運行上面的代碼,你會注意到在控制台有兩個打印信息。一個來自on_pressed事件,該事件在CustomBtn類內部被調用,另一個來自我們綁定屬性改變的btn_pressed函數

你也需要注意到傳遞給on_<property_name>事件的參數及綁定屬性的函數。

def btn_pressed(self, instance, pos):

第一個參數是self,是該函數被定義的類的實例。你可以如下面的方式使用一個內聯函數:

  1.  
    cb = CustomBtn()
  2.  
     
  3.  
    def _local_func(instance, pos):
  4.  
    print ( 'pos: printed from root widget: {pos}'.format(pos=.pos))
  5.  
     
  6.  
    cb.bind(pressed=_local_func)
  7.  
    self.add_widget(cb)

第一個參數是屬性被定義的類的實例。
第二個參數是屬性的新的值。
下面是一個完整的麗日,你能拷貝下來進行實驗。

  1.  
    from kivy.app import App
  2.  
    from kivy.uix.widget import Widget
  3.  
    from kivy.uix.button import Button
  4.  
    from kivy.uix.boxlayout import BoxLayout
  5.  
    from kivy.properties import ListProperty
  6.  
     
  7.  
    class RootWidget(BoxLayout):
  8.  
     
  9.  
    def __init__(self, **kwargs):
  10.  
    super(RootWidget, self).__init__(**kwargs)
  11.  
    self.add_widget(Button(text= 'btn 1'))
  12.  
    cb = CustomBtn()
  13.  
    cb.bind(pressed=self.btn_pressed)
  14.  
    self.add_widget(cb)
  15.  
    self.add_widget(Button(text= 'btn 2'))
  16.  
     
  17.  
    def btn_pressed(self, instance, pos):
  18.  
    print ( 'pos: printed from root widget: {pos}'.format(pos=pos))
  19.  
     
  20.  
    class CustomBtn(Widget):
  21.  
     
  22.  
    pressed = ListProperty([ 0, 0])
  23.  
     
  24.  
    def on_touch_down(self, touch):
  25.  
    if self.collide_point(*touch.pos):
  26.  
    self.pressed = touch.pos
  27.  
    # we consumed the touch. return False here to propagate
  28.  
    # the touch further to the children.
  29.  
    return True
  30.  
    return super(CustomBtn, self).on_touch_down(touch)
  31.  
     
  32.  
    def on_pressed(self, instance, pos):
  33.  
    print ( 'pressed at {pos}'.format(pos=pos))
  34.  
     
  35.  
    class TestApp(App):
  36.  
     
  37.  
    def build(self):
  38.  
    return RootWidget()
  39.  
     
  40.  
     
  41.  
    if __name__ == '__main__':
  42.  
    TestApp().run()

運行結果如下:

property_events_binding

我們的定制按鈕沒有可視的表述,因此顯示一個黑塊。你能觸摸或點擊它以在控制台查看輸出。

九、混合屬性

當定義一個AliasProperty時,通常的做法是定義getter()和setter函數。當getter()和setter()函數使用bind被調用時,它落在了你的肩上。考慮以下代碼:

  1.  
    cursor_pos = AliasProperty(_get_cursor_pos, None, bind=(
  2.  
    'cursor', 'padding', 'pos', 'size', 'focus',
  3.  
    'scroll_x', 'scroll_y'))
  4.  
    '''Current position of the cursor, in (x, y).
  5.  
     
  6.  
    :attr:`cursor_pos` is a :class:`~kivy.properties.AliasProperty`, read-only.
  7.  
    '''

這里cursor_pos是一個AliasProperty,它使用_get_cursor_pos作為getter(),並且setter()為None,表明這是一個只讀屬性。

在最后,當任何使用bind=argument的屬性改變時,on_cursor_pos事件被發送。

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方法中:

  1.  
    def on_touch_move(self, touch):
  2.  
    print(touch.profile)
  3.  
    return super(..., self).on_touch_move(touch)

打印信息為:

['pos', 'angle']

注意:很多人將配置的名字和屬性對應的名字混淆在了一塊。在可用的配置文件里的'angle'不意味着觸摸事件對象有一個angle對象。

對於'pos'配置,屬性pox, x, y是可用的。對於'angle'配置,屬性 a 是可用的。正如我們所說,對於觸摸事件,'pos'是一個托管的配置,但'angle'不是。你能通過檢測'angle'配置是否存在來擴展你的交互:

  1.  
    def on_touch_move(self, touch):
  2.  
    print( 'the touch is at position', touch.pos)
  3.  
    if 'angle' in touch.profile:
  4.  
    print( 'the touch angle is', touch.a)

你能在motionevent文檔中找到一個關於可用的配置的列表。

三、觸摸事件

一個觸摸事件是一個特殊的運動事件,它的屬性is_touch被設置為True。對於所有的觸摸事件,你會自動擁有X和Y坐標,對應着窗口的寬和高。換句話說,所有的觸摸事件有'pox'配置。

(一)觸摸事件基礎

默認情況下,觸摸事件被發送到當前所有的可顯示的部件。這意味着無論事件發生在部件的內部與否,它都能收到事件。

如果你有關於其他GUI的開發經驗,這可能是反直覺的。一般典型的做法是划分屏幕為幾何區域,並且只有坐標在部件區域內部時才發送觸摸或鼠標事件到部件。

當使用觸摸事件工作時,這個要求變得非常的有限制性。強擊,縮放和長按可能來自想了解部件及其如何反應的外部。

為了提供最大的靈活性,Kivy發送事件到所有的部件,並讓它們決定是否響應它們。如果你僅想在部件內部響應觸摸事件,你可以簡單的進行檢測:

  1.  
    def on_touch_down(self, touch):
  2.  
    if self.collide_point(*touch.pos):
  3.  
    #觸摸發生在部件區域的內部,做自己的相應
  4.  
    pass

(二)坐標

一旦你使用了一個帶有矩陣轉換的部件,你必須注意矩陣轉換。一些部件,例如分散(Scatter)有它們自己的矩陣轉換,這意味着觸摸必須能正確的傳遞觸摸坐標到Scatter的子部件。

  • 從父空間到局部空間獲取坐標使用to_local()
  • 從局部空間到父空間獲取坐標使用to_parent()
  • 從局部空間到window空間使用:to_window()
  • 從window空間到局部空間使用:to_widget()

你必須根據上下文使用上面的一個來轉換相應的坐標。看分散(scatter)的實現:

  1.  
    def on_touch_down(self, touch):
  2.  
    #將當前的坐標壓入,為了后面能存儲它
  3.  
    touch.push()
  4.  
     
  5.  
    #轉換觸摸坐標到局部坐標
  6.  
    touch.apply_transform_2d( self.to_local)
  7.  
     
  8.  
    #像平時一樣,發送觸摸到子部件
  9.  
    #在touch里面的坐標是局部坐標
  10.  
    ret = super(..., self).on_touch_down(touch)
  11.  
     
  12.  
    #無論結果如何,不要忘記調用之后彈出你的轉換,
  13.  
    #這樣,坐標會變成父坐標
  14.  
    touch.pop()
  15.  
     
  16.  
    #返回結果(依賴於你的所需)
  17.  
    return ret
  18.  
     

(三)觸摸形狀

如果觸摸有一個形狀,它可以在'shape'屬性中體現。現在,僅僅ShapeRect被暴露:

  1.  
    from kivy.input.shape import ShapeRect
  2.  
     
  3.  
    def on_touch_move(self,touch):
  4.  
    if isinstance(touch.shape, ShapeRect):
  5.  
    print( 'My touch have a rectangle shape of size',
  6.  
    (touch.shape.width, touch.shape.height))

(四)雙擊

雙擊是指在一段時間和范圍內輕點兩次。它由doubletap post-processing模塊來計算。你能測試當前的觸摸是雙擊或不是:

  1.  
    def on_touch_down(self, touch):
  2.  
    if touch.is_double_tap:
  3.  
    print( 'touch is a double tap!')
  4.  
    print( '- interval is', touch.double_tab_time)
  5.  
    print( '- distance between previous is', touch.double_tap_distance)

(五)三擊

三擊是只在一段時間和一定范圍內輕擊三次。它由tripletap post-processing模塊來計算。你能測試當前的觸摸是否為三擊:

  1.  
    def on_touch_down(self, touch):
  2.  
    if touch.is_triple_tap:
  3.  
    print( 'Touch is a triple tap !')
  4.  
    print( ' - interval is', touch.triple_tap_time)
  5.  
    print( ' - distance between previous is', touch.triple_tap_distance)

(六)捕獲觸摸事件

對於父部件使用on_touch_down發送一個觸摸事件到子部件,它是可能的,但通過on_touch_moveon_touch_up就不可以。這可能發生在特定的場景下,例如當一個觸摸事件發生在父部件邊框外部時,父部件決定不通知它的子部件。

當你捕獲了一個事件,你會總是收到移動(move)和彈起(up)事件,但是對於捕獲有一些限制:

  • 你將會收到事件至少兩次:一次來自你的父部件(正常事件),一次來自window(捕獲)。
  • 你可能收到一個事件,但是不是來自你:它可能因為父部件發送給它的子部件。
  • 觸摸坐標沒有轉換到你的部件空間,因為觸摸是來自Window。你需要收動轉換它們。

下面是一個例子來演示如何使用捕獲:

  1.  
    def on_touch_down(self, touch):
  2.  
    if self.collide_point(*touch.pos):
  3.  
     
  4.  
    #如果觸摸檢測來自我們自己的部件,讓我們捕獲它。
  5.  
    touch.grab( self)
  6.  
    # 並響應這次觸摸.
  7.  
    return True
  8.  
     
  9.  
    def on_touch_up(self, touch):
  10.  
    #這里,你不用檢測觸摸碰撞或類似操作,
  11.  
    #你僅需要檢測是否它是一個捕獲的觸摸事件
  12.  
    if touch.grab_current is self:
  13.  
    #OK,當前觸摸事件被派發給我們
  14.  
    #做一些感興趣的操作
  15.  
    print( 'Hello world!')
  16.  
    #不要忘記釋放掉,否則可能會有副作用
  17.  
    touch.ungrab( self)
  18.  
    #最后響應這次觸摸
  19.  
    return True

(七)觸摸事件管理

為了了解觸摸事件如何在部件間被控制和傳播,請參閱部件觸摸事件冒泡機制(Widget touch event bubbling)

4.7 部件

一、部件介紹

在Kivy中,部件是創建GUI接口的基本。它提供了一個畫板,能用來在屏幕上繪畫。它能接收事件並響應它們。有關Widget類的深度的解釋,請參看模塊文檔。

二、操縱部件樹

在Kivy中部件使用樹來管理。你的應用程序有一個根部件,它通常有擁有自己子部件的子部件。子部件被children(一個Kivy的列表屬性(ListProperty)))特征值代表.

部件樹能使用以下方法進行操作:

  • add_widget():添加一個部件作為子部件
  • remove_widget():從子部件列表中移除一個部件
  • clear_widgets():移除所有的子部件

例如如果你想在一個盒子布局(BoxLayout)中添加一個按鈕,你可以:

  1.  
    layout = BoxLayout(padding = 10)
  2.  
    button = Button(text = 'My First Button')
  3.  
    layout.add_widget(button)

按鈕被添加到布局:按鈕的父屬性被設置為layoutlayout將會把button添加到它的子部件列表中。

如果要從layout中移除button,可以:

layout.remove_widget(button)

移除后,按鈕的父屬性被設置為None,layout將從子部件列表中移除button.如果你想將layout中的所有子部件全部移除,可以:

layout.clear_widgets()

注意:永遠不要手動配置子部件列表,除非你真的明白你在做什么。部件樹和一個圖形樹相關聯。例如,如果你添加一個部件到子部件列表,但沒有添加它的畫布到圖形樹,那么部件將稱為一個子部件,但是屏幕上沒有任何東西顯示。更重要的是,你可能在以后調用add_widget, remove_widget, clear_widgets中會出現問題。

三、遍歷部件樹

部件類實例的children中包含所有的子部件,你能容易的遍歷部件樹:

  1.  
    root = BoxLayout()
  2.  
     
  3.  
    #...添加子部件到root...
  4.  
     
  5.  
    for child in root.children
  6.  
    print(child)

但是,這必須要小心使用。如果你試圖使用前面章節提供的方法來修改children,你必須用children列表的拷貝:

  1.  
    for child in root .children [:]:
  2.  
    #配置部件樹,例如移除所有 width<100的部件
  3.  
    if child .width < 100:
  4.  
    root .remove_widget( child)

默認情況下,部件不影響子部件的尺寸和位置。pos特征值是屏幕坐標的絕對位置。(除非你使用了相對布局(relativelayout)和尺寸)。

四、部件Z索引

渲染部件的順序是基於在部件樹的位置。最后的部件的畫布最后被渲染。add_widget有一個index參數,用來設置Z索引:

root.add_widget(widget, index)

五、使用布局管理

布局是一種特殊的部件,它控制它的子部件的尺寸和位置。有不同類型的布局,按照不同的規則自動組織它們的子部件。布局使用size_hintpos_hint屬性來確定它們子部件的尺寸和位置。

(一)盒子布局(BoxLayout)

盒子布局以相鄰的方式(或平行或垂直)來安排他們的子部件,填滿所有的空間。子部件的size_hint屬性能被用來改變被允許的比例或設置固定的尺寸。

box layout

(二)網格布局(GridLayout)

網格布局排列部件在網格內。你必須至少制定網格的一個維度,這樣Kivy才能計算元素的尺寸及如何排列它們。

grid layout

(三)堆疊布局(StackLayout)

堆疊布局一個接一個的排列部件,但是在一個維度上使用了一組尺寸,不要試圖使它們填滿整個空間。它常用來顯示有同樣尺寸的子部件。

stack layout

(四)錨點布局(AnchorLayout)

一種簡單的布局,它僅關注子部件的位置。它允許在一個相對於布局的邊的位置放置子部件。size_hint被忽略。

anchor layout

(五)浮動布局(FloatLayout)

浮動布局允許放置任意位置和尺寸的子部件。默認size_hint(1, 1)將會使每一個子部件有同樣的尺寸作為整個布局,所以,如果你有多個子部件,你可能想改變這個值。你能設置set_hint到(None, None)來使用絕對的尺寸。同樣也可以使用pos_hint設置位置。

float layout

(六)相對布局(RelativeLayout)

有點像FloatLayout,除了子部件的位置是相對於布局位置,而不是屏幕位置。
查看每個布局的文檔,可以用更深入的了解。

[size_hint]和[pos_hint]:

  • [floatlayout]
  • [boxlayout]
  • [gridlayout]
  • [stacklayout]
  • [relativelayout]
  • [anchorlayout]

size_hint是一個關於size_hint_xsize_hint_y的ReferenceListProperty.它接受從0到1,或None的值,默認為(1,1)。這表示如果部件在一個布局中,布局會相對於布局尺寸,在兩個方向上,盡可能為它分配足夠的空間。

設置size_hint到(0.5, 0.8),表示在布局中將會使部件有50%的寬和80%的高可用。

考慮以下代碼:

  1.  
    BoxLayout:
  2.  
    Button:
  3.  
    text: 'Button 1'
  4.  
    #默認size_hint是1, 1,我們不需要明確地指定它
  5.  
    #但是它在這里被設置會更清晰。
  6.  
    size_hint: 1, 1

加載kivy目錄:

  1.  
    cd $KIVYDIR/examples/demo/kivycatalog
  2.  
    python main.py

使用你的Kivy的安裝路徑代替$KIVYDIR。點擊盒子布局左側的按鈕。粘貼上面的代碼到右邊,你將會看到,按鈕占有布局100%的尺寸。

改變size_hint_x/size_hint_y到0.5將會時部件有布局50%的寬/高。

你能看到,雖然我們指定了size_hint_x和size_hint_y到0.5,但是僅僅size_hint_x生效了。這是因為盒子布局在orientation是垂直時控制着size_hint_y,在orientation是水平是控制着size_hint_x。被控制維度的尺寸的計算依賴子部件的總的數目。在這個例子中,僅有一個子部件,因此,它將持有100%的父部件的高度。

讓我們添加另外一個按鈕到布局,看看會發生什么。

 

盒子布局會為它的子部件平分可用的空間。讓我們使用size_hint設置一個按鈕的尺寸。第一個按鈕指定0.5的size_hint_x,第二個按鈕的size_hint_x,默認為1,則總的寬度將變成0.5+1=1.5,第一個按鈕的寬度就會變為0.5/1.5=0.333...,約為1/3的寬度。剩下的盒子布局的寬度分配給另一個按鈕,約為2/3。如果有多個剩余的子部件,他們會進行平分。

如果你想控制部件的絕對大小,你可以設置size_hint_x/size_hint_y,或者二者均為None,這樣部件的widthheight特征值就會使用。

pos_hint是一個字典,默認為空。正如size_hint, 布局對使用pos_hint分別對待,通常你可以添加任何pos特征值(x, y, left, top, center_x, center_y),讓我們實驗下面的代碼,以了解pos_hint:

  1.  
    FloatLayout:
  2.  
    Button:
  3.  
    text: 'We Will'
  4.  
    pos: 100, 100
  5.  
    size_hint:. 2, . 4
  6.  
    Button:
  7.  
    text: 'Wee Wiill'
  8.  
    pos: 200, 200
  9.  
    size_hint:. 4, . 2
  10.  
    Button:
  11.  
    text: 'Rock You'
  12.  
    pos_hint:{ 'x':. 3, 'y':. 6}
  13.  
    size_hint:. 5, . 2

效果如圖:和size_hint一樣,你應當實驗pos_hint來理解它對部件位置的影響。

六、為布局添加一個背景

經常被詢問的關於布局的一個問題是:

如何為一個布局添加一個背景圖片/顏色/視頻/...

布局本質上沒有可視的元素:默認情況下,他們沒有畫布指令。但是你可以添加畫布指令到一個布局實例,正如添加一個背景顏色:

  1.  
    from kivy.graphics import Clolr, Rectangle
  2.  
     
  3.  
    with layout_instance.canvas.before:
  4.  
    Clolr( 0, 1, 0, 1)#綠色;顏色范圍從 0~ 1代替 0~ 255
  5.  
    self.rect = Rectangle(size = layout_instance.size, pos = layout_instance.pos)

不幸的是,這僅僅會在布局的初始位置和尺寸畫一個矩形。當布局的尺寸和位置改變時,為確保矩形被畫在布局內部,我們需要監聽矩形尺寸和位置的任何變動和更新:

  1.  
    with layout_instance.canvas.before:
  2.  
    Color( 0, 1, 0, 1)
  3.  
    self.rect = Rectangle( size = layout_instance.size, pos = layout_instance.pos)
  4.  
     
  5.  
    def update_rect( instance, value):
  6.  
    instance.rect.pos = instance.pos
  7.  
    instance.rect.size = instance.size
  8.  
     
  9.  
    #監聽尺寸和位置的更新
  10.  
    layout_instance.bind(pos=update_rect, size=update_rect)

在kv中:

  1.  
    FloatLayout:
  2.  
    canvas.before:
  3.  
    Color:
  4.  
    rgba:0, 1 , 0 , 1
  5.  
    Rectangle:
  6.  
    #這里的self代表部件,例如BoxLayout
  7.  
    pos:self.pos
  8.  
    size: self.size

kv聲明設置了一個隱性的綁定:最后兩個kv語句確保pos和size值隨着FloatLayout的pos的改變而自動更新。

現在,我們添加一些功能:

  • 純Python方式:
  •  
  • from kivy.app import App 
  • from kivy.graphics import Color,Rectangle
    from kivy.uix.floatlayout import FloatLayout
    from kivy.uix.button import Button

    class RootWidget(FloatLayout):
        """docstring for RootWidget"""
        def __init__(self, **kwargs):
            super(RootWidget, self).__init__(**kwargs)

            #add a button to layout
            self.add_widget(
                Button(
                    text = "modifiy preCondition",
                    size_hint = (.5, .5),
                    pos_hint = {"center_x": .1,"center_y": .1}
                )
            )

    class MainApp(App):
            
        def build(self):
            self.root = root = RootWidget()
            root.bind(size=self._update_rect, pos=self._update_rect)

            with root.canvas.before:
                Color(0,0.5,0,0.5)
                self.rect = Rectangle(size = root.size,pos=root.pos)
            return root

        def _update_rect(self, instance, value):

            self.rect.pos = instance.pos
            self.rect.size  = instance.size

    if __name__ == '__main__':
        MainApp().run()

  • 使用KV語言:
  1.  
    from kivy.app import App
  2.  
    from kivy.lang import Builder
  3.  
     
  4.  
    root = Builder.load_string(
  5.  
    '''
  6.  
    FloatLayout:
  7.  
    canvas.before:
  8.  
    Color:
  9.  
    rgba:0, 1, 0, 1
  10.  
    Rectangle:
  11.  
    pos: self.pos
  12.  
    size: self.size
  13.  
    Button:
  14.  
    text: 'Hello World'
  15.  
    size_hint: .5, .5
  16.  
    pos_hint:{'center_x':.5, 'center_y':.5}
  17.  
    '''
  18.  
    )
  19.  
     
  20.  
    class MainApp(App):
  21.  
    def build(self):
  22.  
    return root
  23.  
     
  24.  
    if __name__ == '__main__':
  25.  
    MainApp().run()

運行效果如下:

添加顏色到背景用一個custom layouts rule/class
如果我們需要使用多重布局的話,這種添加背景到布局實例的方法會變得笨重。為了解決這個問題,我們可以創建布局類的子類,並創建你自己的添加了背景的布局:

  • 使用Python
  1.  
    from kivy.app import App
  2.  
    from kivy.graphics import Color, Rectangle
  3.  
    from kivy.uix.boxlayout import BoxLayout
  4.  
    from kivy.uix.floatlayout import FloatLayout
  5.  
    from kivy.uix.image import AsyncImage
  6.  
     
  7.  
     
  8.  
    class RootWidget(BoxLayout):
  9.  
    pass
  10.  
     
  11.  
     
  12.  
    class CustomLayout(FloatLayout):
  13.  
     
  14.  
    def __init__(self, **kwargs):
  15.  
    # make sure we aren't overriding any important functionality
  16.  
    super(CustomLayout, self).__init__(**kwargs)
  17.  
     
  18.  
    with self.canvas.before:
  19.  
    Color( 0, 1, 0, 1) # green; colors range from 0-1 instead of 0-255
  20.  
    self.rect = Rectangle(size=self.size, pos=self.pos)
  21.  
     
  22.  
    self.bind(size=self._update_rect, pos=self._update_rect)
  23.  
     
  24.  
    def _update_rect(self, instance, value):
  25.  
    self.rect.pos = instance.pos
  26.  
    self.rect.size = instance.size
  27.  
     
  28.  
     
  29.  
    class MainApp(App):
  30.  
     
  31.  
    def build(self):
  32.  
    root = RootWidget()
  33.  
    c = CustomLayout()
  34.  
    root.add_widget(c)
  35.  
    c.add_widget(
  36.  
    AsyncImage(
  37.  
    source= "http://www.everythingzoomer.com/wp-content/uploads/2013/01/Monday-joke-289x277.jpg",
  38.  
    size_hint= ( 1, .5),
  39.  
    pos_hint={ 'center_x': .5, 'center_y': .5}))
  40.  
    root.add_widget(AsyncImage(source= 'http://www.stuffistumbledupon.com/wp-content/uploads/2012/05/Have-you-seen-this-dog-because-its-awesome-meme-puppy-doggy.jpg'))
  41.  
    c = CustomLayout()
  42.  
    c.add_widget(
  43.  
    AsyncImage(
  44.  
    source= "http://www.stuffistumbledupon.com/wp-content/uploads/2012/04/Get-a-Girlfriend-Meme-empty-wallet.jpg",
  45.  
    size_hint= ( 1, .5),
  46.  
    pos_hint={ 'center_x': .5, 'center_y': .5}))
  47.  
    root.add_widget(c)
  48.  
    return root
  49.  
     
  50.  
    if __name__ == '__main__':
  51.  
    MainApp().run()
  • 使用KV語言
  1.  
    from kivy.app import App
  2.  
    from kivy.uix.floatlayout import FloatLayout
  3.  
    from kivy.uix.boxlayout import BoxLayout
  4.  
    from kivy.lang import Builder
  5.  
     
  6.  
     
  7.  
    Builder.load_string( '''
  8.  
    <CustomLayout>
  9.  
    canvas.before:
  10.  
    Color:
  11.  
    rgba: 0, 1, 0, 1
  12.  
    Rectangle:
  13.  
    pos: self.pos
  14.  
    size: self.size
  15.  
     
  16.  
    <RootWidget>
  17.  
    CustomLayout:
  18.  
    AsyncImage:
  19.  
    source: 'http://www.everythingzoomer.com/wp-content/uploads/2013/01/Monday-joke-289x277.jpg'
  20.  
    size_hint: 1, .5
  21.  
    pos_hint: {'center_x':.5, 'center_y': .5}
  22.  
    AsyncImage:
  23.  
    source: 'http://www.stuffistumbledupon.com/wp-content/uploads/2012/05/Have-you-seen-this-dog-because-its-awesome-meme-puppy-doggy.jpg'
  24.  
    CustomLayout
  25.  
    AsyncImage:
  26.  
    source: 'http://www.stuffistumbledupon.com/wp-content/uploads/2012/04/Get-a-Girlfriend-Meme-empty-wallet.jpg'
  27.  
    size_hint: 1, .5
  28.  
    pos_hint: {'center_x':.5, 'center_y': .5}
  29.  
    ''')
  30.  
     
  31.  
    class RootWidget(BoxLayout):
  32.  
    pass
  33.  
     
  34.  
    class CustomLayout(FloatLayout):
  35.  
    pass
  36.  
     
  37.  
    class MainApp(App):
  38.  
     
  39.  
    def build(self):
  40.  
    return RootWidget()
  41.  
     
  42.  
    if __name__ == '__main__':
  43.  
    MainApp().run()

結果如下:

在子類中定義背景,確保它被用在每一個定制布局的實例中。

現在,為了添加一個圖片或顏色到內置的Kivy布局背景中,總體來說,我們需要為布局問題重載kv規則。考慮網格布局:

  1.  
    <GridLayout>
  2.  
    canvas.before:
  3.  
    Color:
  4.  
    rgba: 0 , 1 , 0 , 1
  5.  
    BorderImage:
  6.  
    source: '../examples/widgets/sequenced_images/data/images/button_white.png'
  7.  
    pos: self.pos
  8.  
    size: self.size

下面,我們把這段代碼放入Kivy應用程序:

  1.  
    from kivy.app import App
  2.  
    from kivy.uix.floatlayout import FloatLayout
  3.  
    from kivy.lang import Builder
  4.  
     
  5.  
     
  6.  
    Builder.load_string('''
  7.  
    <GridLayout>
  8.  
    canvas.before:
  9.  
    BorderImage:
  10.  
    # BorderImage behaves like the CSS BorderImage
  11.  
    border: 10 , 10 , 10 , 10
  12.  
    source: '../examples/widgets/sequenced_images/data/images/button_white.png'
  13.  
    pos: self.pos
  14.  
    size: self.size
  15.  
     
  16.  
    <RootWidget>
  17.  
    GridLayout:
  18.  
    size_hint: .9 , .9
  19.  
    pos_hint: { 'center_x': .5, 'center_y': .5}
  20.  
    rows:1
  21.  
    Label:
  22.  
    text: "I don't suffer from insanity, I enjoy every minute of it"
  23.  
    text_size: self.width-20, self.height-20
  24.  
    valign: 'top'
  25.  
    Label:
  26.  
    text: "When I was born I was so surprised; I didn't speak for a year and a half."
  27.  
    text_size: self.width-20, self.height-20
  28.  
    valign: 'middle'
  29.  
    halign: 'center'
  30.  
    Label:
  31.  
    text: "A consultant is someone who takes a subject you understand and makes it sound confusing"
  32.  
    text_size: self.width-20, self.height-20
  33.  
    valign: 'bottom'
  34.  
    halign: 'justify'
  35.  
    '' ')
  36.  
     
  37.  
    class RootWidget(FloatLayout):
  38.  
    pass
  39.  
     
  40.  
     
  41.  
    class MainApp(App):
  42.  
     
  43.  
    def build(self):
  44.  
    return RootWidget()
  45.  
     
  46.  
    if __name__ == ' __main__':
  47.  
    MainApp().run()

結果如下:

由於我們重載了網格布局的規則,任何應用該類的地方都會顯示圖片。

一個動畫背景如何顯示呢?

你可以設置繪畫指令,像Rectangle/BorderImage/Ellipse/...一樣來使用一個特別的材質:

  1.  
    Rectangle:
  2.  
    texture: reference to a texture

我們來顯示一個動畫背景:

  1.  
    from kivy.app import App
  2.  
    from kivy.uix.floatlayout import FloatLayout
  3.  
    from kivy.uix.gridlayout import GridLayout
  4.  
    from kivy.uix.image import Image
  5.  
    from kivy.properties import ObjectProperty
  6.  
    from kivy.lang import Builder
  7.  
     
  8.  
     
  9.  
    Builder.load_string('''
  10.  
    <CustomLayout>
  11.  
    canvas.before:
  12.  
    BorderImage:
  13.  
    # BorderImage behaves like the CSS BorderImage
  14.  
    border: 10, 10, 10, 10
  15.  
    texture: self.background_image.texture
  16.  
    pos: self.pos
  17.  
    size: self.size
  18.  
     
  19.  
    <RootWidget>
  20.  
    CustomLayout:
  21.  
    size_hint: .9, .9
  22.  
    pos_hint: {'center_x': .5, 'center_y': .5}
  23.  
    rows: 1
  24.  
    Label:
  25.  
    text: "I don't suffer from insanity, I enjoy every minute of it"
  26.  
    text_size: self.width-20, self.height-20
  27.  
    valign: 'top'
  28.  
    Label:
  29.  
    text: "When I was born I was so surprised; I didn't speak for a year and a half."
  30.  
    text_size: self.width-20, self.height-20
  31.  
    valign: 'middle'
  32.  
    halign: 'center'
  33.  
    Label:
  34.  
    text: "A consultant is someone who takes a subject you understand and makes it sound confusing"
  35.  
    text_size: self.width-20, self.height-20
  36.  
    valign: 'bottom'
  37.  
    halign: 'justify'
  38.  
    ''')
  39.  
     
  40.  
     
  41.  
    class CustomLayout(GridLayout):
  42.  
     
  43.  
    background_image = ObjectProperty(
  44.  
    Image(
  45.  
    source= '../examples/widgets/sequenced_images/data/images/button_white_animated.zip',
  46.  
    anim_delay= .1))
  47.  
     
  48.  
     
  49.  
    class RootWidget(FloatLayout):
  50.  
    pass
  51.  
     
  52.  
     
  53.  
    class MainApp(App):
  54.  
     
  55.  
    def build(self):
  56.  
    return RootWidget()
  57.  
     
  58.  
    if __name__ == '__main__':
  59.  
    MainApp().run()

為了理解到底發生了什么,先看13行:

texture:self.background_image.texture

這表明BorderImage的材質屬性在background_image更新時都將被更新。我們定義了background_image屬性在40行:

background_image = ObjectProperty(...)

這段代碼設置background_miage是一個ObjectProperty,在那兒我們添加了一個Image部件。一個Image部件有一個textuer屬性,self.background_image.texture設置了一個對於texture的引用。Image部件支持動畫:圖片的材質在動畫改變時會被更新,並且BorderImage指令的材質跟着更新了。

您還可以自定義數據的紋理貼圖。更多信息請參閱Texture文檔。

七、嵌套布局

當然,關於如何擴展這部分內容應該是很有趣的!

gthank-沒有實際內容

八、尺寸和坐標度量

Kivy的默認長度單位是像素(pixel),所有尺寸和位置都使用它。你也可以使用別的單位以獲得更好的跨平台的效果。

可用的單位有pt, mm, cm, inch, dp, sp.你可以在metrics文檔中了解它們的用法。

你可以用screen應用模擬不同的設備來測試你的應用程序。

九、用屏幕管理進行屏幕分離

如果你的應用程序由不同的屏幕組成,你可能想有一個容易的方式來從一個屏幕導航到另一個屏幕。幸運的是,有一個ScreenManager類,允許你分別定義屏幕,並從一個屏幕到另外一個屏幕設置基本轉換(TransitionBase)

4.8 圖形

一、畫布介紹

部件繪畫的表現使用畫布來完成。你可以將它看作一個無限制的畫板,或者一個繪畫指令的集合。
在你的畫布中,你可以應用的指令有很多,但是最主要的兩個是:

  • 上下文指令(context instructions)
  • 頂點指令(vertex instructions)

上下文指令不畫任何東西,但是它們改變頂點指令的結果。

畫布包含指令的兩種子集合:canvas.before, canvas.after。這些指令在canvas前或后執行。這些指令直到用戶訪問時才創建。

為了添加一個畫布指令到部件,你需要使用畫布上下文:

  1.  
    class MyWidget(Widget):
  2.  
    def __init__( self, **kwargs):
  3.  
    super(MyWidget, self).__init__(**kwargs)
  4.  
    with self. canvas:
  5.  
    #為主畫布添加你的指令
  6.  
     
  7.  
    with self.canvas. before:
  8.  
    #渲染前執行
  9.  
     
  10.  
    with self.canvas. after:
  11.  
    #渲染后執行

二、上下文指令

上下文指令操縱Opengl上下文。你可以旋轉、轉換、拉伸你的畫布。你也可以附着一個材質或改變繪畫的顏色。這是最常用的,但是其它也是很有用的,比如:

  1.  
    with self.canvas.before:
  2.  
    Color( 1, 0, .4, mode= 'rgb')

三、繪畫指令

繪畫指令簡單的包括畫一條直線或一個多邊形,復雜的包括貝塞爾曲線:

  1.  
    with self.canvas:
  2.  
    #畫一條直線,使用默認的顏色
  3.  
    Line(points=(x 1, y 1, x 2, y 2, x 3, y 3))
  4.  
     
  5.  
    #畫一個半透明的紅色的正方形
  6.  
    Color( 1, 0, 0, . 5, mode='rgba')
  7.  
    Rectangle(pos=self.pos, size=self.size)

四、配置指令

有時,你想更新或移除你的指令,根據你的需要可以使用不同的方式:
你可以保持一個你的指令的引用並更新它們:

  1.  
    class MyWidget(Widget):
  2.  
    def __init__(self, **kwargs):
  3.  
    super(MyWidget, self).__init_ _(**kwargs)
  4.  
    with self. canvas:
  5.  
    self.rect = Rectangle(pos= self.pos, size= self.size)
  6.  
     
  7.  
    self.bind(pos= self.update_rect)
  8.  
    self.bind(size= self.update_rect)
  9.  
     
  10.  
    def update_rect(self, *args):
  11.  
    self.rect.pos = self.pos
  12.  
    self.rect.size = self.size

或者你可以清理你的畫布並啟動刷新:

  1.  
    class MyWidget(Widget):
  2.  
    def __init__(self, **kwargs):
  3.  
    super(MyWidget, self).__init_ _(**kwargs)
  4.  
    self.draw_my_stuff()
  5.  
     
  6.  
    self.bind(pos= self.draw_my_stuff)
  7.  
    self.bind(size= self.draw_my_stuff)
  8.  
     
  9.  
    def draw_my_stuff(self):
  10.  
    self.canvas.clear()
  11.  
     
  12.  
    with self. canvas:
  13.  
    self.rect = Rectangle(pos= self.pos, size= self.size)

注意更新的指令被認為是最佳實踐,因為它需要更少的開銷和避免創建新的指令。

4.9 Kivy語言

一、語言背后的思想

當你的應用程序變得更復雜時,構建部件樹和明確的聲明綁定將變得冗長和難以維護。KV語言試圖克服這些缺點。

KV語言(有時被叫kvlang,或kivy語言),允許你以聲明的方式來創建你的部件樹,並以一種自然的方式綁定部件屬性或回調函數。針對UI,它支持快速原型和敏捷改動。它也使得邏輯和用戶接口能更好的分離。

二、如何加載KV

有兩種方式來加載KV代碼:

  • 通過名字約定
    Kivy查找你的應用程序類的小寫的同名KV文件,如果它以'App'結尾則去掉它,例如:

    MyApp -> my.kv

如果這個文件定義了一個根部件,它將會附着到應用程序的根特征值,並用它作為應用程序部件樹的根。

  • Builder
    你可以告訴Kivy直接加載一個字符串或一個文件。如果這個字符串或文件定義了根部件,它將被返回。

    Builder.load_file('path/to/file.kv')

或者

Builder.load_string('kv_string')

三、管理上下文

一個KV源構成的規則,用來描述部件的內容。你可以有一個根規則和任何數量的類或模板規則。

根規則通過聲明你的根部件類來聲明,不需要任何縮進,后面跟着冒號(:),並且被設置為應用程序實例的根特征值。

Widget:

一個類規則,有一對尖括號(<>)包括部件類名組成,后面跟冒號(:),定義類的實例如何被生動地表達:

<MyWidget>:

和Python一樣,規則使用縮進進行界定,和良好的Python習慣一樣,縮進的每一級別最好是4個空格。

有三個關鍵字來指定KV語言:

  • app:總是引用你的應用程序的實例。
  • root:引用當前規則中的根部件/模板。
  • self:引用當前部件。

四、特殊的語法

有兩個特殊語法來為整個KV上下文定義值:

  • 為了從KV中訪問Python的模塊和類:
  1.  
    # :import name x .y .z
  2.  
    # :import isdir os .path .isdir
  3.  
    # :import np numpy

上面的代碼等價於:

  1.  
    from x.y import z as name
  2.  
    from os.path import isdir
  3.  
    import numpy as np
  • 為了設置一個全部變量:
    #:set name value

等價於:

    name = value

五、實例化子部件

為了聲明部件的子部件,僅在規則里面聲明這些子部件即可:

  1.  
    MyRootWidget:
  2.  
    BoxLayout:
  3.  
    Button:
  4.  
    Button:

上面的例子定義了一個MyRootWidget的實例作為我們的根部件,它有一個子部件是BoxLayout的實例。BoxLayout進一步有兩個Button類的子部件。在Python代碼中應該是這樣:

  1.  
    root = MyRootWidget()
  2.  
    box = BoxLayout()
  3.  
    box.add_widget(Button())
  4.  
    box.add_widget(Button())
  5.  
    root.add_widget( box)

你會發現在KV中,僅用很少的代碼,易寫並易讀。

當然,在Python中,你可以傳遞關鍵字參數到你的部件中。例如,設置一個GridLayout的列的數目,我們可以這樣寫:

grid = GridLayout(cols = 3)

在KV中,你可以直接在規則中設置子部件的屬性:

  1.  
    GridLayout:
  2.  
    cols: 3

這個值被評估為一個Python表達式,並且表達式中所有的屬性值都將被監聽。例如在Python中:

  1.  
    grid = GridLayout(cols = len(self. data))
  2.  
    self.bind( data = grid.setter('cols'))

當你的數據變化時,顯示跟着更新,在KV中只需這樣:

  1.  
    GridLayout:
  2.  
    cols: len(root.data)

注意,當屬性名以小寫字母開頭時,部件名首字母應當大寫。遵循PEP8 Naming Conventions是被鼓勵的。

六、事件綁定

在KV語言中,你可以使用":"語法來綁定事件:

  1.  
    Widget:
  2.  
    on_size: my_callback()

你也可以使用args關鍵字傳遞參數:

  1.  
    TextInput:
  2.  
    on_text: app.search(args[1])

更復雜的表達式可能類似這樣:

pos:self.center_x - self.texture_size[0] / 2, self.center_y - self.texture_size[1] / 2

這個表達式監聽center_x, center_y, texture_size的變動。如果其中一個發生了改變,表達式將會更新pos字段。

你也可以在KV語言中處理on_事件。例如輸入框有一個聚焦(focus)屬性,它將自動生成on_focus事件:

  1.  
    TextInput:
  2.  
    on_focus: print(args)

七、擴展畫布

KV語言可以這樣來定義你的畫布指令:

  1.  
    MyWidget:
  2.  
    canvas:
  3.  
    Color:
  4.  
    rgba: 1 , .3 , .8 , .5
  5.  
    Line:
  6.  
    points: zip(self.data.x, self.data.y)

當屬性值改變時它們將更新,當然,你也可以使用canvas.before和canvas.after.

八、引用部件

在一個部件樹中,經常需要訪問/引用其他的部件。KV語言提供了一個使用id's的方法來做這些工作。將它們認為是只能用於Kv語言類級別變量。看下面代碼:

  1.  
    <MyFirstWidget>:
  2.  
    Button:
  3.  
    id: f_but
  4.  
    TextInput:
  5.  
    text: f_but.state
  6.  
     
  7.  
    <MySecondWidget>:
  8.  
    Button:
  9.  
    id: s_but
  10.  
    TextInput:
  11.  
    text: s_but.state

一個id被限制到它被聲明的作用域內,所以在<MySecondWidget>外面s_but不能被訪問。

id是一個部件的弱引用(weakref)並且不是部件本身。因此,存儲id不能防止部件被垃圾回收。為了證明:

  1.  
    <MyWidget>:
  2.  
    label_widget: label_widget
  3.  
    Button:
  4.  
    text: 'Add Button'
  5.  
    on_press: root.add_widget(label_widget)
  6.  
    Button:
  7.  
    text: 'Remove Button'
  8.  
    on_press: root.remove_widget(label_widget)
  9.  
    Label:
  10.  
    id: label_widget
  11.  
    text: 'widget'

上面的代碼中,雖然一個到label_widget的引用被存儲到MyWidget中,但是因為它僅僅是一個弱引用,一旦別的引用被移除,它不足以保持對象存活。因此,當移除按鈕被點擊后(將移除其他的引用)窗口將重新計算尺寸(調用垃圾回收導致重新檢測label_widget),當點擊添加按鈕來添加部件,一個引用錯誤將發生(ReferenceError:weakly-referenced object no longer exists)

為了保持部件存活,一個對label_widget的引用必須被保持。可以使用id.self或label_widget.self做到。正確的方式如下:

  1.  
    <MyWidget>:
  2.  
    label_widget: label_widget.__self__

九、在Python代碼中訪問Kv語言定義的部件

考慮以下在my.kv中的代碼:

  1.  
    <MyFirstWidget>:
  2.  
    # both these variables can be the same name and this doesn't lead to
  3.  
    # an issue with uniqueness as the id is only accessible in kv.
  4.  
    txt_inpt: txt_inpt
  5.  
    Button:
  6.  
    id: f_but
  7.  
    TextInput:
  8.  
    id: txt_inpt
  9.  
    text: f_but.state
  10.  
    on_text: root.check_status(f_but)

在myapp.py:

  1.  
    ...
  2.  
    class MyFirstWidget(BoxLayout):
  3.  
     
  4.  
    txt_inpt = ObjectProperty( None)
  5.  
     
  6.  
    def check_status(self, btn):
  7.  
    print( 'button state is: {state}'.format(state=btn.state))
  8.  
    print( 'text input text is: {txt}'.format(txt=self.txt_inpt))
  9.  
    ...

txt_inpt被作為ObjectProperty初始化:

txt_inpt = ObjectProperty(None)

這是效果導致self.txt_inpt是None。在KV語言中,這個屬性更新被id:txt_inpt引用的持有TextInput的實例。

txt_inpt:txt_inpt

從這點向上,self.txt_inpt持有一個被id txt_input標識的部件的引用並且能被用在類的任何地方,正如在check_status函數中一樣。對照這個函數,你僅僅需要傳遞id到你想用的地方。

你可以使用ids來訪問帶id標識的對象,這是一種更簡單的方法:

  1.  
    <Marvel>
  2.  
    Label:
  3.  
    id: loki
  4.  
    text: 'loki: I AM YOUR GOD!'
  5.  
    Button:
  6.  
    id: hulk
  7.  
    text: "press to smash loki"
  8.  
    on_release: root.hulk_smash()

在你的Python代碼中:

  1.  
    class Marvel(BoxLayout):
  2.  
     
  3.  
    def hulk_smash(self):
  4.  
    self.ids.hulk.text = "hulk: puny god!"
  5.  
    self.ids[ "loki"].text = "loki: >_<!!!" # alternative syntax

當你的kv文件被解析時,kivy收集所有的帶id標簽的部件,並放置它們到self.ids字典中。這意味着你能以字典的風格來迭代這些部件並訪問它們。

  1.  
    for key, val in self.ids.items():
  2.  
    print( "key={0}, val={1}". format(key, val))

注意,雖然self.ids很簡潔,它被認為是使用ObjectProperty的最佳實踐。但是創建一個字典的引用,將會提供更快的訪問速度並更加清晰。

十、動態類

考慮下面代碼:

  1.  
    <MyWidget>:
  2.  
    Button:
  3.  
    text: "Hello world, watch this text wrap inside the button"
  4.  
    text_size: self.size
  5.  
    font_size: '25sp'
  6.  
    markup: True
  7.  
    Button:
  8.  
    text: "Even absolute is relative to itself"
  9.  
    text_size: self.size
  10.  
    font_size: '25sp'
  11.  
    markup: True
  12.  
    Button:
  13.  
    text: "Repeating the same thing over and over in a comp = fail"
  14.  
    text_size: self.size
  15.  
    font_size: '25sp'
  16.  
    markup: True
  17.  
    Button:

為了替代重復的代碼,我們可以使用模板來代替:

  1.  
    <MyBigButt@Button>:
  2.  
    text_size: self.size
  3.  
    font_size: '25sp'
  4.  
    markup: True
  5.  
     
  6.  
    <MyWidget>:
  7.  
    MyBigButt:
  8.  
    text: "Hello world, watch this text wrap inside the button"
  9.  
    MyBigButt:
  10.  
    text: "Even absolute is relative to itself"
  11.  
    MyBigButt:
  12.  
    text: "repeating the same thing over and over in a comp = fail"
  13.  
    MyBigButt:

這個被規則聲明的類繼承自按鈕類。它允許我們改變默認值,並為每一個實例創建綁定而不用在Python那邊添加任何新的代碼。

十一、在多個部件中重用樣式

看下面的在my.kv中的代碼:

  1.  
    <MyFirstWidget>:
  2.  
    Button:
  3.  
    on_press: self.text(txt_inpt.text)
  4.  
    TextInput:
  5.  
    id: txt_inpt
  6.  
     
  7.  
    <MySecondWidget>:
  8.  
    Button:
  9.  
    on_press: self.text(txt_inpt.text)
  10.  
    TextInput:
  11.  
    id: txt_inpt

在myapp.py中

  1.  
    class MyFirstWidget(BoxLayout):
  2.  
     
  3.  
    def text(self, val):
  4.  
    print( 'text input text is: {txt}'.format(txt=val))
  5.  
     
  6.  
    class MySecondWidget(BoxLayout):
  7.  
     
  8.  
    writing = StringProperty( '')
  9.  
     
  10.  
    def text(self, val):
  11.  
    self.writing = val

因為兩個類共同使用相同的.kv風格。如果我們為兩個部件重用風格,這將使得設計簡化。你可以在my.kv中這樣寫代碼:

  1.  
    <MyFirstWidget,MySecondWidget>:
  2.  
    Button:
  3.  
    on_press: self.text(txt_inpt.text)
  4.  
    TextInput:
  5.  
    id: txt_inpt

用一個逗號(,)來分離類名,所有的類將都有同樣的kv屬性。

十二、使用KV語言設計

使用Kivy語言的一個目標就是分離邏輯和表現。表現層使用kv文件來表示,邏輯使用py文件來表示。

(一)py文件中寫代碼

讓我們開始一個小例子,首先,在main.py文件中:

  1.  
    import kivy
  2.  
    kivy.require( '1.0.5')
  3.  
     
  4.  
    from kivy.uix.floatlayout import FloatLayout
  5.  
    from kivy.app import App
  6.  
    from kivy.properties import ObjectProperty, StringProperty
  7.  
     
  8.  
     
  9.  
    class Controller(FloatLayout):
  10.  
    '''Create a controller that receives a custom widget from the kv lang file.
  11.  
     
  12.  
    Add an action to be called from the kv lang file.
  13.  
    '''
  14.  
    label_wid = ObjectProperty()
  15.  
    info = StringProperty()
  16.  
     
  17.  
    def do_action(self):
  18.  
    self.label_wid.text = 'My label after button press'
  19.  
    self.info = 'New info text'
  20.  
     
  21.  
     
  22.  
    class ControllerApp(App):
  23.  
     
  24.  
    def build(self):
  25.  
    return Controller(info= 'Hello world')
  26.  
     
  27.  
    if __name__ == '__main__':
  28.  
    ControllerApp().run()

在這個例子中,我們創建了一個帶有兩個屬性的控制類:

  • info:接收一些文本
  • label_wid接收標簽(label)部件

另外,我們創建了一個do_action()方法來使用這些屬性。它將會改變info文本和label_wid部件的文本。

(二)在controller.kv中布局

執行一個沒有相應的.kv文件的應用程序可以運行,但是沒有任何東西被顯示到屏幕上。這是被期望的,因為控制類沒有部件在里面,它僅僅是一個FloatLayout。我們能圍繞Controller類在一個controller.kv文件中創建UI,當我們運行ControllerApp時它會被加載。這將如何實現及什么文件被加載都在kivy.app.App.load_kv()方法中被描述。

  1.  
    #:kivy 1.0
  2.  
     
  3.  
    <Controller>:
  4.  
    label_wid: my_custom_label
  5.  
     
  6.  
    BoxLayout:
  7.  
    orientation: 'vertical'
  8.  
    padding: 20
  9.  
     
  10.  
    Button:
  11.  
    text: 'My controller info is: ' + root.info
  12.  
    on_press: root.do_action()
  13.  
     
  14.  
    Label:
  15.  
    id: my_custom_label
  16.  
    text: 'My label before button press'

在垂直布局的BoxLayout中,有一個標簽和一個按鈕。看起來很簡單,有3個事情將被做:

  1. 從Controller使用數據。一旦在controller中info屬性被改變,表達式text:'My Controller info is:' + root.info將會自動更新。

  2. 傳遞數據到Controller。表達式id:my_custom_label被賦值給id為my_custom_label的標簽。於是,在表達式label_wid:my_custom_label中使用my_custom_label傳遞部件Label的實例到你的Controller。

  3. 使用Controller的on_press方法創建一個定制的回調函數。

    • root和self被保留為關鍵字,可用在任何地方。root代表規則內的根部件,self代表當前部件。

    • 在規則內你可以使用任何id聲明,同root和self一樣。例如,你可以在on_press()中這樣:

      Button:
      on_press:root.do_action();my_custom_label.font_size = 18

現在,我們運行main.py, controller.kv將會被自動加載,按鈕和標簽也將顯示並響應你的觸摸事件。

4.10集成其他框架

在Kivy內部使用Twisted框架

gthank:Twisted是用Python實現的基於事件驅動的網絡引擎框架。Twisted誕生於2000年初,在當時的網絡游戲開發者看來,無論他們使用哪種語言,手中都鮮有可兼顧擴展性及跨平台的網絡庫。Twisted的作者試圖在當時現有的環境下開發游戲,這一步走的非常艱難,他們迫切地需要一個可擴展性高、基於事件驅動、跨平台的網絡開發框架,為此他們決定自己實現一個,並從那些之前的游戲和網絡應用程序的開發者中學習,汲取他們的經驗教訓。Twisted支持許多常見的傳輸及應用層協議,包括TCP、UDP、SSL/TLS、HTTP、IMAP、SSH、IRC以及FTP。Twisted對於其支持的所有協議都帶有客戶端和服務器實現,同時附帶有基於命令行的工具,使得配置和部署產品級的Twisted應用變得非常方便。

你可以使用kivy.support.install_twisted_reactor函數來安裝一個運行在kivy事件循環里面的twisted反應器。任何傳遞到該函數的參數和關鍵字都會被傳遞給threadedselect反應器交錯函數。通常有一個參數傳遞twisted的reactor.startRunning.

警告:不像默認的Twisted反應器,安裝的反應器不會處理任何的信號,除非你設置了installSignalHandlers關鍵字參數為1.這將使kivy來和平時一樣來處理信號,除非你明確地想twisted反應器來處理信號。

kivy的樣例中含一個關於twisted服務端和客戶端的小例子。服務端程序有一個簡單的twisted服務運行並在日志上記錄所有的信息。客戶端程序能發送消息到服務端,並打印它發送及響應它的信息。例子基於twisted文檔的的簡單的Echo 例子,你可以在下面的鏈接找到它們:

為了測試這個例子,首先運行echo_server_app.py,接着運行echo_client_app.py。服務端將會使用簡單的echo消息來回應任何來自客戶端發送的消息。

服務端:

  1.  
    #在導入和使用反應器之前,install_twisted_rector必須首先被調用
  2.  
    from kivy.support import install_twisted_reactor
  3.  
    install_twisted_reactor()
  4.  
     
  5.  
     
  6.  
    from twisted.internet import reactor
  7.  
    from twisted.internet import protocol
  8.  
     
  9.  
     
  10.  
    class EchoProtocol(protocol.Protocol):
  11.  
    def dataReceived(self, data):
  12.  
    response = self.factory.app.handle_message(data)
  13.  
    if response:
  14.  
    self.transport.write(response)
  15.  
     
  16.  
     
  17.  
    class EchoFactory(protocol.Factory):
  18.  
    protocol = EchoProtocol
  19.  
     
  20.  
    def __init__(self, app):
  21.  
    self.app = app
  22.  
     
  23.  
     
  24.  
    from kivy.app import App
  25.  
    from kivy.uix.label import Label
  26.  
     
  27.  
     
  28.  
    class TwistedServerApp(App):
  29.  
    def build(self):
  30.  
    self.label = Label(text= "server started\n")
  31.  
    reactor.listenTCP( 8000, EchoFactory(self))
  32.  
    return self.label
  33.  
     
  34.  
    def handle_message(self, msg):
  35.  
    self.label.text = "received: %s\n" % msg
  36.  
     
  37.  
    if msg == "ping":
  38.  
    msg = "pong"
  39.  
    if msg == "plop":
  40.  
    msg = "kivy rocks"
  41.  
    self.label.text += "responded: %s\n" % msg
  42.  
    return msg
  43.  
     
  44.  
     
  45.  
    if __name__ == '__main__':
  46.  
    TwistedServerApp().run()

客戶端:

  1.  
    #在導入和使用反應器之前,install_twisted_rector必須首先被調用
  2.  
    from kivy.support import install_twisted_reactor
  3.  
    install_twisted_reactor()
  4.  
     
  5.  
    #一個簡單的客戶端,它能發送消息到echo服務端
  6.  
    from twisted.internet import reactor, protocol
  7.  
     
  8.  
     
  9.  
    class EchoClient(protocol.Protocol):
  10.  
    def connectionMade(self):
  11.  
    self.factory.app.on_connection( self.transport)
  12.  
     
  13.  
    def dataReceived(self, data):
  14.  
    self.factory.app.print_message(data)
  15.  
     
  16.  
     
  17.  
    class EchoFactory(protocol.ClientFactory):
  18.  
    protocol = EchoClient
  19.  
     
  20.  
    def __init__(self, app):
  21.  
    self.app = app
  22.  
     
  23.  
    def clientConnectionLost(self, conn, reason):
  24.  
    self.app.print_message( "connection lost")
  25.  
     
  26.  
    def clientConnectionFailed(self, conn, reason):
  27.  
    self.app.print_message( "connection failed")
  28.  
     
  29.  
     
  30.  
    from kivy.app import App
  31.  
    from kivy.uix.label import Label
  32.  
    from kivy.uix.textinput import TextInput
  33.  
    from kivy.uix.boxlayout import BoxLayout
  34.  
     
  35.  
    #一個簡單的kivy應用程序,帶有一個文本框來輸入消息,
  36.  
    #並有一個文本標簽來顯示所有的從服務端返回的消息
  37.  
    class TwistedClientApp(App):
  38.  
    connection = None
  39.  
     
  40.  
    def build(self):
  41.  
    root = self.setup_gui()
  42.  
    self.connect_to_server()
  43.  
    return root
  44.  
     
  45.  
    def setup_gui(self):
  46.  
    self.textbox = TextInput(size_hint_y=. 1, multiline=False)
  47.  
    self.textbox.bind(on_text_validate= self.send_message)
  48.  
    self.label = Label(text= 'connecting...\n')
  49.  
    self.layout = BoxLayout(orientation= 'vertical')
  50.  
    self.layout.add_widget( self.label)
  51.  
    self.layout.add_widget( self.textbox)
  52.  
    return self.layout
  53.  
     
  54.  
    def connect_to_server(self):
  55.  
    reactor.connectTCP( 'localhost', 8000, EchoFactory( self))
  56.  
     
  57.  
    def on_connection(self, connection):
  58.  
    self.print_message( "connected succesfully!")
  59.  
    self.connection = connection
  60.  
     
  61.  
    def send_message(self, *args):
  62.  
    msg = self.textbox.text
  63.  
    if msg and self. connection:
  64.  
    self.connection.write(str( self.textbox.text))
  65.  
    self.textbox.text = ""
  66.  
     
  67.  
    def print_message(self, msg):
  68.  
    self.label.text += msg + "\n"
  69.  
     
  70.  
    if __name_ _ == '__main__':
  71.  
    TwistedClientApp().run()

4.11最佳實踐&4.12高級圖形技術

一、最佳實踐

(一)設計你的應用程序代碼

(二)處理窗口尺寸大小的改變

(三)管理資源

  • 材質(Atlas)

  • 緩存(Cache)

    • 圖片
    • 文本

(四)跨可台

(五)小技巧

  • 皮膚

  • 使用模塊

    • 管理
    • 注入
    • 屏幕
  • Kivy-Remote-Shell

二、高級圖形技術(Advanced Graphics)

  • 創建你自己的着色器
  • 在幀緩沖中渲染
  • 優化

 

作者:gthank
鏈接:https://www.jianshu.com/p/ac1d6180391b
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM