(この記事を書いた当時はRecycleView
をまだ扱った事がなかったので使わないで実装していますが、使った方が綺麗なコードになります。)
Kivyにて以下のようにSlideして現れるMenuを作りました。
上の図では分かりませんが、Menuの項目は6つ以上ありスクロールして選ぶことが出来るようになっています。
まずはアニメーション抜きでMenuを作る
# -*- coding: utf-8 -*-
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.factory import Factory
Builder.load_string(r'''
<MenuItem@ButtonBehavior+Label>:
font_size: self.height
canvas.before:
Color:
rgba: [.2, .2, .2, 1] if self.state == 'down' else [0, 0, 0, 0]
Rectangle:
pos: self.pos
size: self.size
Color:
rgba: [1, 1, 1, 1]
Line:
rectangle: [self.x, self.y, self.width, self.height]
on_press: print("'{}' button was pressed.".format(self.text))
<Menu@BoxLayout>:
layout: id_layout
orientation: 'vertical'
padding: [5]
spacing: 20
Label:
size_hint_y: 1
text: 'Menu'
font_size: self.height
ScrollView:
size_hint_y: 5
do_scroll_x: False
GridLayout:
id: id_layout
size_hint_y: None # B
height: self.minimum_height # B
cols: 1
row_default_height: self.parent.height / 7 # A
row_force_default: True # A
spacing: 10
''')
def _test():
menu = Factory.Menu()
MenuItem = Factory.MenuItem
add_widget = menu.layout.add_widget
for text in 'Python Ruby C++ Smalltalk Javascript C HTML Swift'.split():
add_widget(MenuItem(text=text))
runTouchApp(menu)
if __name__ == '__main__':
_test()
MenuのInstanceを作りMenu.layout.add_widget()を呼ぶことでMenuの項目を追加する事ができます。ここではMenuItemというクラスをわざわざ用意してはいますが、別に何のWidgetでも構いません。
実装ですが、Menu項目の親にBoxLayoutではなくGridLayoutを使っているのには理由があり、それはA行B行で使っている便利なPropertyの為です。
A行は子Widgetの高さを強制的にScrollViewの高さの7分の1にする為のもので、これによって子Widget達が持っている[高さに関するProperty(heightやsize_hint_y)]を無視して無理やり高さを揃えることができます。
B行はGridLayoutの高さを常に[全ての子Widgetが入る最低限の大きさ]になるよう伸縮させる為のもので、適切に縦スクロールさせるのに必要です。又Kivy1.9.2からはBoxLayoutにもこのminimum_heightが追加されるようなので、GridLayoutの代わりに使うことでもっと簡潔なコードを書けるかもしれません。
アニメーションする土台を作る
次に上で作ったMenuを乗せる土台Widgetを作っていくのですが、ここでは最終的にModalView(俗に言うモーダルウィンドウ)として呼び出せるMenuを作ろうとしているので親クラスをModalViewにしています。
# -*- coding: utf-8 -*-
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.animation import Animation
class SlideinModalView(Factory.ModalView):
def open(self, *args, **kwargs):
size_hint_x = self.size_hint_x
self.pos_hint = {'x': -size_hint_x}
animation = Animation(
pos_hint={'x': (1 - size_hint_x) / 2},
duration=0.3,
transition='in_cubic'
)
super(SlideinModalView, self).open(*args, **kwargs)
animation.start(self)
def dismiss(self, *args, **kwargs):
size_hint_x = self.size_hint_x
animation = Animation(
pos_hint={'x': -size_hint_x},
duration=0.3,
transition='in_out_bounce'
)
super(SlideinModalView, self).dismiss(*args, **kwargs)
animation.start(self)
def _test():
import textwrap
modalview = SlideinModalView(size_hint=(0.8, 0.8,))
root = Builder.load_string(textwrap.dedent('''
FloatLayout:
Button:
id: id_button
size_hint: None, None
size: 300,100
text: 'open SlideinModalView'
'''))
root.ids.id_button.bind(on_press=modalview.open)
runTouchApp(root)
if __name__ == '__main__':
_test()
やってる事はModalViewを開くMethod(open)と閉じるMethod(dismiss)を上書きしてアニメーションの処理を追加しているだけです。
中で使っているAnimationのKeyword引数ですが
- durationはアニメーション全体の長さ(秒)
- stepはアニメーションの細かさ(何秒毎に値を書き換えるか)
- transitionはどのように値を変化させるか。假に一定の速度で変化させたければ'linear'にすればいい。使える値の一覧はこちら。
- そして残りのKeyword引数がアニメーションさせたいPropertyとその最終値の指定になっています。(上記の場合はpos_hint)
そしてアニメーションの対象とするWidgetは.start()の引数で指定します。アニメーション対象のWidgetの指定とアニメーション対象のPropertyの指定が別々なのが少し分かりづらいですね。
上のSlideinModalViewにMenuを乗っける
# -*- coding: utf-8 -*-
from kivy.base import runTouchApp
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.animation import Animation
Builder.load_string(r'''
同じなので略
''')
class SlideinModalView(Factory.ModalView):
同じなので略
class SlideinMenu(SlideinModalView):
def __init__(self, **kwargs):
super(SlideinMenu, self).__init__(**kwargs)
self.menu = menu = Factory.Menu()
self.layout = menu.layout
self.add_widget(menu)
def _test():
import textwrap
slideinmenu = SlideinMenu(size_hint=(0.8, 0.8,))
layout = slideinmenu.layout
for text in 'Python Ruby C++ Smalltalk Javascript C Swift'.split():
layout.add_widget(Factory.MenuItem(text=text))
root = Builder.load_string(textwrap.dedent('''
FloatLayout:
Button:
id: id_button
size_hint: None, None
size: 300,100
text: 'open menu'
'''))
root.ids.id_button.bind(on_press=slideinmenu.open)
runTouchApp(root)
if __name__ == '__main__':
_test()
見た目を改良
ModalViewが元々もってる背景色がなんともダサいので自前で描画することにします。
<SlideinMenu>:
canvas:
Clear: # ModalViewが元々持っていた描画命令を削除
Color:
rgba: 0, 0, 0, 0.7
Rectangle:
pos: 0, 0
size: self.size if self.parent is None else self.parent.size
###ソースコード全体
環境
- Python 3.4.3
- Kivy 1.9.1