目標
こんなふうに, シングルタップとダブルタップで別の動作をするボタンを作成する.
具体的には, シングルタップで「+1」, ダブルタップで「数字を 0 に戻す」動作を行うボタンを実装する.
サンプルコード(ダブルタップ実装前)
左のボタンを押すと表示された数値に +1, 右のボタンを押すと表示された数値に +2 するシンプルなボタン.
これを修正してダブルタップ処理を追加する.
以下にコードを記載する.
1. double_tap_sample.py
プログラムを動かすメインファイル.
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from layout import DoubleTapSample
Window.size = (500, 500)
Builder.load_file('double_tap_sample.kv')
class DoubleTapSampleApp(App):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def build(self):
return DoubleTapSample()
if __name__ == '__main__':
DoubleTapSampleApp().run()
2. double_tap_sample.kv
Label に数字を表示, PlusOneButton と PlusTwoButton の on_press: で, それぞれ root.plus_one(), root.plus_two() を呼び出している.
#:kivy 1.2.0
#:import PlueOneButton layout.PlusOneButton
#:import PlueTwoButton layout.PlusTwoButton
#:import DoubleTapSample layout.DoubleTapSample
<DoubleTapSample>:
id: double_tap_samle
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1., 0.5)
Label:
text: str(root.number)
font_size: 100
BoxLayout:
size_hint: (1., 0.5)
PlusOneButton:
text: '+1'
font_size: 100
on_press: root.plus_one()
PlusTwoButton:
text: '+2'
font_size: 100
on_press: root.plus_two()
3. layout.py
各ボタンから呼び出された処理の具体的な内容を記載する python ファイル.
PlusOneButton, PlusTwoButton は Kivy に最初からあるボタンクラスを継承している.
from kivy.uix.button import Button
from kivy.properties import NumericProperty
from kivy.uix.screenmanager import Screen
class DoubleTapSample(Screen):
number = NumericProperty(0)
def plus_one(self):
self.number += 1
def plus_two(self):
self.number += 2
class PlusOneButton(Button):
pass
class PlusTwoButton(Button):
pass
サンプルコード(実装後)
改良後のサンプルコードがこちら.
左のボタンをシングルタップすると表示された数字に+1, ダブルタップで数字を 0 に戻せている.
1. double_tap_sample.py
前回と変化がないので省略
2. double_tap_sample.kv
#:kivy 1.2.0
#:import DoubleClickButton layout.DoubleClickButton
#:import PlusOneButton layout.PlusOneButton
#:import PlueTwoButton layout.PlusTwoButton
#:import DoubleTapSample layout.DoubleTapSample
<DoubleTapSample>:
id: double_tap_sample
BoxLayout:
orientation: 'vertical'
BoxLayout:
size_hint: (1., 0.5)
Label:
text: str(root.number)
font_size: 100
BoxLayout:
size_hint: (1., 0.5)
PlusOneButton:
id: plus_one_button
text: '+1'
font_size: 100
PlusTwoButton:
text: '+2'
font_size: 100
on_press: root.plus_two()
<DoubleClickButton>:
background_color:(0, 0, 0, 0)
background_normal:''
canvas.before:
Color:
rgba: (0/256, 175/256, 216/256, 1) if self.touched else (88/256,88/256,88/256,1)
Rectangle:
pos: self.pos
size: self.size
ダブルタップを実装したので on_press: root.plus_one()
がなくなっている.
タップ時にボタンの色を変えるために
<DoubleClickButton>:
background_color:(0, 0, 0, 0)
background_normal:''
canvas.before:
Color:
rgba: (0/256, 175/256, 216/256, 1) if self.touched else (88/256,88/256,88/256,1)
Rectangle:
pos: self.pos
size: self.size
を追加したのが大きな変化である.
3. layout.py
from kivy.app import App
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.properties import NumericProperty, BooleanProperty
from kivy.uix.screenmanager import Screen
class DoubleTapSample(Screen):
number = NumericProperty(0)
def plus_one(self, *args):
self.number += 1
def plus_two(self):
self.number += 2
def reset_num(self):
self.number = 0
class DoubleClickButton(Button):
pass
class PlusOneButton(DoubleClickButton):
scheduled_event = None
touched = BooleanProperty(False)
def on_touch_down(self, touch):
if self.scheduled_event is not None:
self.scheduled_event.cancel()
self.scheduled_event = None
if self.collide_point(*touch.pos):
self.touched = True
if touch.is_double_tap:
App.get_running_app().root.reset_num()
else:
double_tap_wait_s = 0.2
self.scheduled_event = Clock.schedule_once(App.get_running_app().root.plus_one, double_tap_wait_s)
def on_touch_up(self, touch):
if self.collide_point(*touch.pos):
self.touched = False
class PlusTwoButton(Button):
pass
まずは, 数値を 0 に戻すための def reset_num()
を追加.
on_touch_down()
でタッチを検知し, ダブルタップかどうかを判別している(詳細は後述).
touched = BooleanProperty(False)
はタッチ中にボタンの色を変えるために使う. タッチされた時に, on_touch_down()
内で True
になり, タッチ終了時に呼ばれる, on_touch_up()
で False
に戻す.
個人的にハマったところ
1. ダブルタップ時にシングルタップの処理が呼ばれないようにする
ダブルタップの判定には, kivyのドキュメンテーションのサンプルコードを参考にダブルタップを実装し, else 文でシングルタップ時の処理を書けば良いように思えるが...
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
if touch.is_double_tap:
App.get_running_app().root.reset_num()
else:
App.get_running_app().root.plus_one()
上のコードを実行するとシングルタップ処理の後でダブルタップ処理が呼ばれてしまう
参考にしたのはPython3 & Kivy: React only to double tap "not" single tapのコード
def on_touch_down(self, touch):
if self.scheduled_event is not None:
self.scheduled_event.cancel()
self.scheduled_event = None
if self.collide_point(*touch.pos):
self.touched = True
if touch.is_double_tap:
App.get_running_app().root.reset_num()
else:
double_tap_wait_s = 0.2
self.scheduled_event = Clock.schedule_once(App.get_running_app().root.plus_one, double_tap_wait_s)
kivy の Clock 機能を使って, 0.2 秒後にシングルタップ時の処理が行われるように登録を行う.
もし, 0.2 秒以内にもう一度タップされたら登録されたイベントは削除, ダブルタップ処理が行われる.
また, if self.collide_point(*touch.pos):
の判定がないと, 画面のどこを押してもシングルタップ or ダブルタップ処理が行われてしまう.
2. 別クラスに記載された処理を呼び出す.
初めは, on_press: root.plus_one()
で root(DoubleTapSample クラス)に書かれた plus_one 処理を呼び出していた. 同じ処理を .py ファイルで書くためにApp.get_running_app().root.reset_num()
を使っている.
def plus_one(self, *args):
は *args がないと clock takes 1 positional argument but 2 were given というエラーになる.
まとめ
ダブルタップとシングルタップで処理を変えたいというときの実装方法がわかった.