イベント処理 - Kivy Advent Calendar 2013
相変わらずネタ切れ/時間切れで申し訳ないです。イベント処理の問題で少し加筆 (http://d.hatena.ne.jp/cheeseshop/20131205) したので、その件で少し書くことにします。
Kivyのイベント処理は、各ウィジェットクラスが「あるタイミング」で「イベントに結びつけた関数群を実行する」というものです。今回のAdvent Calendarで使ってきたものを挙げると
記事 | クラス | イベント | 動作 |
---|---|---|---|
2013-12-01 ファイル選択 | Button | on_release | Load/Cancelボタンを押したら実行(ダミー) |
2013-12-03 QRコード | TextInput | on_text_validate | Enterキーを押したらQRコード生成 |
2013-12-05 スライド | RstDocument | on_touch_up | タップしたらポップアップを開く |
2013-12-08 ポップアップ | FloatLayout | on_touch_up | タップしたらバブルを開く |
2013-12-09 画面転換 | Spinner | on_text | 選択肢を変更したら画面効果差替 |
2013-12-09 画面転換 | Button | on_press | ボタンを押したら別スクリーンに転換 |
2013-12-10 アプリ復帰 | App | on_pause | 別のアプリへの切替時 |
2013-12-10 アプリ復帰 | App | on_resume | 別のアプリからの復帰時 |
2013-12-11 カード | Scatter | on_touch_up | タッチしたらひっくり返す |
2013-12-13 流行語 | Button | on_press | ボタンを押したら喋る |
2013-12-14 写真 | StackLayout | on_touch_up | タッチしたらカメラ起動 |
2013-12-14 写真 | PythonActivity | on_activity_result | カメラ終了のタイミング |
2013-12-15 テキスト共有 | Button | on_press | ボタンを押したらメーラ起動 |
2013-12-16 Webブラウザ | Button | on_press | ボタンを押したらブラウザ起動 |
2013-12-16 Webブラウザ | Button | on_press | ボタンを押したらブラウザ起動 |
2013-12-18 Launcher | Button | on_press | ボタンを押したらKivyLauncher起動 |
2013-12-19 キャラ移動 | Button | on_press | ボタンを押したらキャラクタ移動 |
2013-12-19 キャラ移動 | Animation | on_progress | 画像を順次変えてアニメ |
2013-12-19 キャラ移動 | Animation | on_complete | 排他制御を解除 |
まあ「ボタンを押して○○する」のが多いです。でも中にはアニメ、アプリ、アクティビティのようにKivyにおけるユーザ操作とは関係なくイベントが発生するものがあります。
on_touch_up / on_touch_downについて
さて、問題だったのは「2013-12-05 スライド」のon_touch_upです。ここでは画面をダブルタップしたらreStructureTextソースをポップアップを見せていましたが、ここではon_touch_upの戻り値で問題が解決したかのように書いていました。実際にはそうではありませんでした。一見「今開いたスライド」のソースを見せていましたが、実は
- 全部のスライド (page01〜page03) のソースが一斉に開く (どれが一番上になるかは不定)
- 外部をタップしたときには3つとも閉じた
というものでした。必ずしも表示しているスライドのソースが見えなかったということです。
何故こうなるのかといえば、on_touch_upというのはタッチした場所にあるウィジェットへ送られるイベントではなく、単に「タッチパネルから指が離れた」というイベントだからです。
つまり、「タッチした場所以外に置かれたウィジェット」に対してもon_touch_upは発生するし、しかもScreenManagerやCarouselのように画面転換によって「非表示になっているウィジェット」にもon_touch_upは発生してしまうのでした。
: orientation: 'vertical' Label: text: 'LABEL1' on_touch_down: self.text = 'LABEL1: DOWN' on_touch_up: self.text = 'LABEL1: UP' Label: text: 'LABEL2' on_touch_down: self.text = 'LABEL2: DOWN' on_touch_up: self.text = 'LABEL2: UP' Label: text: 'LABEL3' on_touch_down: self.text = 'LABEL3: DOWN' on_touch_up: self.text = 'LABEL3: UP'
: orientation: 'vertical' Label: text: 'LABEL1' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL1: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL1: UP') Label: text: 'LABEL2' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL2: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL2: UP') Label: text: 'LABEL3' on_touch_down: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL3: DOWN') on_touch_up: self.collide_point(*args[1].pos) and setattr(self, 'text', 'LABEL3: UP')
(まあ一般的にイベントハンドラは、kv言語よりもメソッドで書く方が楽だと思います…)
ScreenManagerやCarouselの非表示ウィジェットについては、それが現在表示されているものか調べることができますから、それを条件にしてイベントを受け付けるか受け付けないかを切り替えることになります。
self.collide_point(*touch.pos) | 自身にタッチしたか |
self.manager.current_screen is self | ScreenManagerが自身を表示しているか |
self.parent.current_slide is self | Carouselが自身を表示しているか (2013-12-05 スライドはこれを使用) |
on_touch_up/on_touch_downは、全画面で使う分にはこういった問題はない (2013-12-08 ポップアップなど) のですが、複数あったり非表示要素がある場合は注意が必要です。