この記事では、KivyのScreenManagerクラスを使って複数の画面を遷移させる方法を紹介します。また、Transitionを使って画面遷移時のアニメーション効果を設定する方法や、kv言語の動的クラスルールを使用してコードを少なくする方法も紹介します。
KivyのScreenManager
KivyのScreenManagerはScreenManagerクラスとScreenクラスで構成されます。ScreenManagerクラスは各画面を制御し、Screenクラスは各画面を構成します。アニメーション効果付きで複数の画面を遷移させることができます。アニメーション効果を設定するにはTransitionを使います。
Transitionの種類
Transitionは8種類あります。デフォルトはSlideTransitionです。
- NoTransition
アニメーションなしで瞬時に画面を切り替える。 - SlideTransition
[left, right, up or down]方向に画面をスライドする。 - CardTransition
popモード:新しい画面が前の画面にスライドする。
pushモード:前の画面の上に新しい画面が[left, right, up or down]の方向にスライドする。 - SwapTransition
画面が波打つようにスワップする。 - FadeTransition
画面がフェードイン、フェードアウトする - WipeTransition
画面が右から左にワイプする。 - FallOutTransition
画面の中央に向かってフェードアウトして、その背後にある新しい画面が現れる。 - RiseInTransition
新しい画面が画面の中央から徐々に大きくなって現れる。
文章では伝わりづらいので動画で確認してください。このアプリのコードは最後の章で紹介しています。
ScreenManager書き方①~Screenサブクラスを使用~
ScreenManagerはScreenクラスで画面を作り、ScreenManagerクラスでScreenをコントロールします。Screenは最低で2つ必要で、書かないといけない事がいくつかあります。
- ScreenManagerクラスやScreenクラス、トランジションをインポートする。
- Screenクラスを継承したサブクラスを画面の数分定義する。
- ScreenManagerクラスを継承したサブクラスを定義しルートウィジェットとして返す。
- currentプロパティにScreenの名前を設定する。
- transitionプロパティでアニメーション効果を設定する。
- Screenの名前を設定する。
- Screenサブクラスにウィジェットを構成する。
サンプルコード
Screenクラスのサブクラスを使用した書き方のサンプルを紹介します。
screenmanager1.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition, SlideTransition
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class ControlScreenManager(ScreenManager):
def switch_to_screen1(self):
self.transition = SlideTransition(direction='left', duration=1)
self.current = 'screen1'
def switch_to_screen2(self):
self.transition = FadeTransition(duration=1)
self.current = 'screen2'
class ScreenManager1(App):
def build(self):
return ControlScreenManager()
if __name__ == '__main__':
ScreenManager1().run()
screenmanager1.kv
<ControlScreenManager>:
Screen1:
Screen2:
<Screen1>:
name: 'screen1'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen 1\n\nNext to FadeTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 2'
on_release: app.root.switch_to_screen2()
<Screen2>:
name: 'screen2'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen 2\n\nNext to SlideTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 1'
on_release: app.root.switch_to_screen1()
Pythonコード解説
- ScreenManagerクラスやScreenクラス、トランジションをインポートする。
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition, SlideTransition
トラジションは使う分だけリストで記述します。
- Screenクラスを継承したサブクラスを画面の数分定義する。
class Screen1(Screen):
pass
class Screen2(Screen):
pass
ウィジェットの構成とスタイルはkvファイルで記述し、イベント処理などはpyファイルに記述します。何の処理をしない場合でも上記のように空のクラス定義が必要です。
- ScreenManagerクラスを継承したサブクラスを定義しルートウィジェットとして返す。
class ControlScreenManager(ScreenManager):
pass
class ScreenManager1(App):
def build(self):
return ControlScreenManager()
ScreenManagerクラスを継承したサブクラスを定義します。そしてそれをbuildメソッドで返すようにします。
- currentプロパティにScreenの名前を設定する。
def switch_to_screen1(self):
self.current = 'screen1'
def switch_to_screen2(self):
self.current = 'screen2'
currentプロパティはの現在の表示しているScreenを参照するので、Screenを一意の名前で指定します。この例ではkvファイルで定義したボタンからswitch_to_screen()メソッドを呼び出しているためメソッド内でそれぞれの画面に対応する名前を指定しています。
<Screen1>:
name: 'screen1' #nameプロパティの値と同じにする。
<Screen2>:
name: 'screen2' #nameプロパティの値と同じにする。
kvファイルのnameプロパティの値と同一にする必要があります。
- transitionプロパティでアニメーション効果を設定する。
def switch_to_screen1(self):
self.transition = SlideTransition(direction='left', duration=1)
def switch_to_screen2(self):
self.transition = FadeTransition(duration=1)
Transitionクラスの引数にアニメーション効果を指定します。トラジションのプロパティもここで指定します。durationプロパティは画面遷移する間隔の継続時間を指定します。デフォルトは400ms(0.4秒)です。
kvコード解説
- Screenの名前を設定する。
<Screen1>:
name: 'screen1'
<Screen2>:
name: 'screen2'
nameプロパティに画面の一意の名前を設定します。pyファイルで定義したcurrentプロパティと同じ値にする必要があります。
- Screenサブクラスにウィジェットを構成する。
<Screen1>:
name: 'screen1'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen 1\n\nNext to FadeTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 2'
on_release: app.root.switch_to_screen2()
クラスルールでScreenクラスのウィジェットを構成します。この例ではLabelウィジェットとButtonウィジェットを定義し、on_releaseイベントでは次の画面に対応するswitch_to_screen()を呼び出しています。app.rootキーワードについては下記の記事をご覧ください。
<Screen1>:
on_release: app.root.switch_to_screen2()
screen1からscreen2へ遷移する場合はswitch_to_screen2()を呼び出しています。
<Screen2>:
on_release: app.root.switch_to_screen1()
最後のページのscreen2ではscreen1に戻りたいのでswitch_to_screen1()を呼び出しています。
- ルールのオーバーライド
<ControlScreenManager>:
Screen1:
Screen2:
ルートウィジェットにScreenクラスのルールをオーバーライドしています。これをしないとブラックスクリーンになり何も表示されません。
ScreenManager書き方➁~Dynamic Class rulesを使用~
動的クラスルールを使用し、ScreenManager ロパティをkvファイルに書くことも可能です。この方法の方がコードが少なくて済むので、コードの構造によっては、動的クラスルールを使用する方が良いかもしれません。
サンプルコード
screenmanager2.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager
class ControlScreenManager(ScreenManager):
pass
class ScreenManager2(App):
def build(self):
return ControlScreenManager()
if __name__ == '__main__':
ScreenManager2().run()
screenmanager2.kv
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition
<ControlScreenManager>:
Screen1:
Screen2:
<Screen1@Screen>:
name: 'screen1'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen 1\n\nNext to FallOutTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 2'
on_release:
app.root.transition = FallOutTransition(duration=1)
app.root.current = 'screen2'
<Screen2@Screen>:
name: 'screen2'
BoxLayout:
orientation: 'vertical'
Label:
text: 'Screen 2\n\nNext to RiseInTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 1'
on_release:
app.root.transition = RiseInTransition(duration=1)
app.root.current = 'screen1'
Pythonコード解説
Screenクラスを継承したサブクラスはPythonコードに書かなくても動作しました。Pythonコードで処理を記述したい場合は、必要なScreenのサブクラスだけ書けばよいので無駄なコードを書かなくて済みます。
kvコード解説
trasitionやcurrentプロパティもkv言語で記述することができます。
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition
インポート文は以下の構文になっています。
#:import < Alias > < Module >
<ControlScreenManager>:
Screen1:
Screen2:
Screenクラスのルールをオーバーライドしています。
name: 'screen1'
nameプロパティで画面の名前を定義します。
on_release:
app.root.transition = FallOutTransition(duration=1)
app.root.current = 'screen2'
on_releaseもしくはon_pressイベントでtransitionとcurrentを設定します。currentとtransitionはScreenクラスで記述することはできないのでapp.rootを経由してScreenManagerクラスで記述します。
画面間のデータの受け渡し
ScreenManagerを使って画面間のデータを参照するにはidsを駆使して取得します。またScreenManagerクラスのon_enter()とget_screen()、それからon_kv_post()のこれらのメソッドがidsを参照する際に役に立ちます。idsの詳細については下記の記事をご覧ください。
on_enterメソッド
on_enter()はScreenManagerの画面が表示される直前に呼び出されるメソッドです。このメソッドをオーバーライドすることで、画面が表示される際に特定の処理を実行することができます。idsを取得する際はこのメソッドを使用すると便利です。
get_screenメソッド
get_screen()はScreenManager内の画面を取得するためのメソッドです。このメソッドは画面間でidsのデータを参照したり、特定の画面の操作を行う場合に便利です。
on_kv_postメソッド
kvファイルが読み込まれたタイミングで呼び出されるメソッドです。idsの生成が出来ていない場所でidsを参照したい場合にこのメソッドを使用するとidsが参照できる場合があります。
サンプルコード
このサンプルコードでは2つの画面間でids辞書を参照してデータを受け渡す方法が学べます。Screen1ではScreen2のLabel.textを変更し、Screen2ではScreen1のLabel.textを変更しています。
screenmanager3.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class Screen1(Screen):
def on_kv_post(self, base_widget):
print(f"Screen1 - ids : {self.ids}")
if self.ids:
print(self.ids.label1.text)
else:
print("label1 not found in Screen1")
class Screen2(Screen):
def on_enter(self):
print(self.ids.label2.text)
print(f"Screen2 - ids : {self.ids}")
class ControlScreenManager(ScreenManager):
def get_label1(self):
screen1 = self.get_screen('screen1')
print(f"ControlScreenManager - ids : {screen1.ids}")
if 'label1' in screen1.ids:
screen1.ids.label1.text = "Update label1 from root widget"
else:
print("label1 not found in Screen1")
def get_label2(self):
screen2 = self.get_screen('screen2')
print(f"ControlScreenManager - ids: {screen2.ids}")
if 'label2' in screen2.ids:
screen2.ids.label2.text = "Update labe2 from root widget"
else:
print("label2 not found in Screen2")
class ScreenManager3(App):
def build(self):
return ControlScreenManager()
if __name__ == '__main__':
ScreenManager3().run()
screenmanager3.kv
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition
<ControlScreenManager>:
Screen1:
Screen2:
<Screen1@Screen>:
name: 'screen1'
BoxLayout:
orientation: 'vertical'
Label:
id: label1
text: 'Screen 1\n\nNext to FallOutTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 2'
on_release:
app.root.transition = FallOutTransition(duration=1)
app.root.current = 'screen2'
app.root.get_label2()
<Screen2@Screen>:
name: 'screen2'
BoxLayout:
orientation: 'vertical'
Label:
id: label2
text: 'Screen 2\n\nNext to RiseInTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 1'
on_release:
app.root.transition = RiseInTransition(duration=1)
app.root.current = 'screen1'
app.root.get_label1()
Pythonコード解説
このサンプルコードは下記の構成になっています。
<ControlScreenManager>
+--- Screen1
+---+--- Label
+---+---+--- id: label1
+---+--- Button
+--- Screen2
+---+--- Label
+---+---+--- id: label2
+---+--- Button
class Screen1(Screen):
def on_kv_post(self, base_widget):
print(f"Screen1 - ids : {self.ids}")
if self.ids:
print(self.ids.label1.text)
else:
print("label1 not found in Screen1")
Kivyアプリを起動するとScreen1が表示されます。通常、on_enterメソッドをオーバーライドしてその中に処理を記述しますが、最初の画面の場合、ids辞書がまだ生成されていないのでidsが取得できません。そのためon_kv_postメソッドを使用し、確実にkvファイルの読み込みが終わりids辞書が生成されるタイミングでidsを参照するようにします。
def on_kv_post(self, base_widget):
最初の画面のみ確実にidsを取得できるようにon_kv_post()を定義します。引数base_widgetにはルートウィジェットのControlScreenManager()が渡されます。
if self.ids:
print(self.ids.label1.text)
else:
print("label1 not found in Screen1")
idsが存在している場合に処理をします。これを書かないとエラーになります。ids辞書が取れなかった場合にelse文で処理を分岐しています。
class Screen2(Screen):
def on_enter(self):
print(self.ids.label2.text)
print(f"Screen2 - ids : {self.ids}")
2番目に表示される画面はScreen2です。ここではon_enterメソッドを定義してids辞書を参照する処理をしています。2番目の画面は既にids辞書が生成されているため、on_enterメソッドを使用することができます。
Screen1とScreen2ではそれぞれが別のウィジェットツリーのため、相互にidを取得できません。これはidのスコープ制限によるものです。これらのidを相互に取得するにはControlScreenManagerの方で記述します。ControlScreenManagerはルートウィジェットであり、ScreenManagerを継承しているのでそれぞれのScreenを制御することができます。
def get_label1(self):
screen1 = self.get_screen('screen1')
print(f"ControlScreenManager - ids : {screen1.ids}")
if 'label1' in screen1.ids:
label1 = screen1.ids.label1
label1.text = "Update label1 from root widget"
else:
print("label1 not found in Screen1")
通常、異なるスコープのidを参照する場合はidsの名前空間を遡って記述しますが、ScreenManagerの場合は各画面に名前を付けているので、その名前を使ってidを参照することができます。
- 画面名を取得する : get_screen( < 画面名 > )
- idを参照する : < 画面名 >.ids.< id名 >.< プロパティ >
screen1 = self.get_screen('screen1')
get_screenの引数に画面名を指定し変数に代入しています。
if 'label1' in screen1.ids:
screen1.ids.label1.text = "Update label1 from root widget"
Pythonのin演算子を使用してids内に’label1’が存在するかチェックし、これが存在する場合は処理を実行するようにします。ここではlabel1のtextを変更しています。
kvコード解説
<Screen1@Screen>:
name: 'screen1'
Button:
on_release:
app.root.get_label2()
Screen1ではget_label2()を呼び出して、Screen2のLabel.textを変更しています。
<Screen2@Screen>:
name: 'screen2'
Button:
on_release:
app.root.get_label1()
Screen2ではget_label1()を呼び出して、Screen1のLabel.textを変更しています。
ScreenManageのTrasitionのサンプル
この記事の冒頭で紹介した動画のサンプルコードを掲載しておきます。このコードは動的クラスルールを使用しています。ここではPythonコードで処理をしてないのでScreenクラスを定義していません。そのためコードが見やすくなり、コード量も少し減ったような気がします。
clearcolor()でアニメーション効果の背景色を設定する
ScreenManageのclearcolorメソッドでTrasition効果中の背景色を設定できますが、下記のいくつかのTrasitionにしか効果がないようです。
- FadeTransition
- WipeTransition
- FallOutTransition
- RiseInTransition
screenmanager_ex.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager
class ControlScreenManager(ScreenManager):
pass
class ScreenManager_ex(App):
def build(self):
return ControlScreenManager()
if __name__ == '__main__':
ScreenManager_ex().run()
screenmanager_ex.kv
#:import NoTransition kivy.uix.screenmanager.NoTransition
#:import FadeTransition kivy.uix.screenmanager.FadeTransition
#:import SlideTransition kivy.uix.screenmanager.SlideTransition
#:import SwapTransition kivy.uix.screenmanager.SwapTransition
#:import CardTransition kivy.uix.screenmanager.CardTransition
#:import WipeTransition kivy.uix.screenmanager.WipeTransition
#:import FallOutTransition kivy.uix.screenmanager.FallOutTransition
#:import RiseInTransition kivy.uix.screenmanager.RiseInTransition
<ControlScreenManager>:
Screen1:
Screen2:
Screen3:
Screen4:
Screen5:
Screen6:
Screen7:
Screen8:
<Screen1@Screen>:
name: 'screen1'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id1
text: 'Screen 1\n\nNext to SlideTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 2'
on_release:
app.root.transition = SlideTransition(direction='left', duration=1)
app.root.current = 'screen2'
<Screen2@Screen>:
name: 'screen2'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id2
text: 'Screen 2\n\nNext to FadeTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 3'
on_release:
app.root.transition = FadeTransition(duration=1,clearcolor=(255/255, 99/255, 71/255, 0.3))
app.root.current = 'screen3'
<Screen3@Screen>:
name: 'screen3'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id3
text: 'Screen 3\n\nNext to SwapTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 4'
on_release:
app.root.transition = SwapTransition(duration=1)
app.root.current = 'screen4'
<Screen4@Screen>:
name: 'screen4'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id4
text: 'Screen 4\n\nNext to CardTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 5'
on_release:
app.root.transition = CardTransition(duration=1)
app.root.current = 'screen5'
<Screen5@Screen>:
name: 'screen5'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id5
text: 'Screen 5\n\nNext to WipeTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 6'
on_release:
app.root.transition = WipeTransition(duration=1,clearcolor=(255/255, 99/255, 71/255, 0.3))
app.root.current = 'screen6'
<Screen6@Screen>:
name: 'screen6'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id6
text: 'Screen 6\n\nNext to FallOutTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 7'
on_release:
app.root.transition = FallOutTransition(duration=1,clearcolor=(255/255, 99/255, 71/255, 0.3))
app.root.current = 'screen7'
<Screen7@Screen>:
name: 'screen7'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id7
text: 'Screen 7\n\nNext to RiseInTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 8'
on_release:
app.root.transition = RiseInTransition(duration=1,clearcolor=(255/255, 99/255, 71/255, 0.3))
app.root.current = 'screen8'
<Screen8@Screen>:
name: 'screen8'
BoxLayout:
orientation: 'vertical'
Label:
id: label_id8
text: 'Screen 8\n\nNext to NoTransition'
halign: 'center'
font_size: 30
Button:
text: 'Go to Screen 1'
on_release:
app.root.transition = NoTransition(duration=1)
app.root.current = 'screen1'
Comment