Kivyでアプリを作成していて、画像の拡大をピンチ操作でも行えるようにしたかった。
それっぽい動きを実現することができたのでメモ。とりあえず下記がサンプル。
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy import platform
from kivy.clock import Clock
from kivy.graphics import Point
class TouchAction(BoxLayout):
image_size = 50
image_pos_x = 50
image_pos_y = 50
move_pre_pos = {} # 移動前のタッチ位置保持用
touch_pair = [] # タッチペア
shape = None
def __init__(self, **kwargs) -> None:
super(TouchAction, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1.0 / 30)
def update(self, dt):
self.draw_shapes()
def draw_shapes(self):
self.clear_shapes()
with self.canvas:
self.shape = Point(points=[self.image_pos_x, self.image_pos_y], pointsize=self.image_size, source= 'test.png')
def clear_shapes(self):
if self.shape is not None:
self.canvas.remove(self.shape)
self.shape = None
def on_image_touch_down(self, touch):
#タッチ位置の保持
self.move_pre_pos[touch.uid] = (touch.x,touch.y)
def on_image_touch_move(self, touch):
w,h = self.size #画面サイズ
if len(self.move_pre_pos.keys()) >= 2:
px1,py1 = self.move_pre_pos[touch.uid] #処理中のタッチ
for uid in self.move_pre_pos.keys():
# 3本指以上のタッチは無視
if uid != touch.uid:
px2,py2 = self.move_pre_pos[uid] #同時されているタッチ
# uidのペアを保持
self.touch_pair = [touch.uid,uid]
# 移動前の2点間の距離
bdx = abs(px1 - px2)
bdy = abs(py1 - py2)
# 移動前の2点間の中点
bax = (px1 + px2) / 2
bay = (py1 + py2) / 2
# 移動後の2点間の距離
adx = abs(touch.x - px2)
ady = abs(touch.y - py2)
# 移動前の2点間の中点
aax = (touch.x + px2) / 2
aay = (touch.y + py2) / 2
if (bdx <= adx) and (bdy <= ady):
#ピンチアウト
self.image_size = self.image_size + int(max(adx-bdx,ady-bdy)) # xとyの移動前後で差分が大きい方
# 範囲外判定
if self.image_size > 500 :
self.image_size = 500
if (bdx >= adx) and (bdy >= ady):
#ピンチイン
self.image_size = self.image_size - int(max(bdx-adx,bdy-ady)) # xとyの移動前後で差分が大きい方
# 範囲外判定
if self.image_size < 10 :
self.image_size = 10
# 2本指スワイプ
mx = round(aax - bax) #移動距離
my = round(aay - bay ) #移動距離
self.image_pos_x = self.image_pos_x + mx
self.image_pos_y = self.image_pos_y + my
# 範囲外の制御
if self.image_pos_x < 0:
self.image_pos_x = 0
if self.image_pos_y < 0:
self.image_pos_y = 0
if (self.image_pos_x + self.image_size) > w:
self.image_pos_x = (w - self.image_size)
if (self.image_pos_y + self.image_size) > h:
self.image_pos_y = (h - self.image_size)
elif touch.uid in self.touch_pair:
# 2指操作の片割れの場合は処理抜け
return
else:
# 通常(1本指)のタッチイベントがあればこっちに
pass
self.move_pre_pos[touch.uid] = (touch.x,touch.y)
def on_image_touch_up(self, touch):
# 解放
if touch.uid in self.move_pre_pos.keys():
del self.move_pre_pos[touch.uid]
class TestTouchActionApp(App):
def build(self):
return TouchAction()
if __name__ == '__main__':
TestTouchActionApp().run()
kv側は省略します。サンプルを動かす場合はTouchActionクラスのウィジェットにイベントを適当に紐づけてサンプルの画像(test.png)を置けば多分動く。
on_touch_down: root.on_image_touch_down(args[1])
on_touch_move: root.on_image_touch_move(args[1])
on_touch_up: root.on_image_touch_up(args[1])
実装した内容の考え方としては、タッチ毎にイベントオブジェクトのuidに番号が振られているみたいなのでそれを利用することにしました。
タッチのタイミングでuidをキーにして辞書配列に位置を保持しておいて、処理中のタッチの前位置と、同時にタッチしているもう一方の位置を引っ張れるようにして判定しています。
サンプルでメインになる関数は下記3つ。
・on_image_touch_down:タッチダウン時の処理(でしょうね)。タッチ中の位置を保持。
・on_image_touch_move:タッチ移動時の処理(でしょうね)。2本指ジェスチャーの判定。
・on_image_touch_up:タッチを離す時の処理(でしょうね)。タッチ位置保持を解放。
クラス変数として以下を用意しています。
・move_pre_pos:タッチ位置保持用
・touch_pair:タッチペア判定用
その他の変数や関数は確認用に用意したものなので、実装には特に関係ありません。
ざっくり解説すると、タッチ中の2本の指間の距離を判定して距離が広がっていたらピンチアウト、距離が縮まっていたらピンチインという判定をしています。また、グルメサイトの地図とかである2本で動かしたときだけスワイプするような動きも、2点間の中点の移動方向を使ってそれっぽいことができました。
ちなみに、サンプルではタッチ前後の移動距離を増減に使っていますが、操作感によって変化量は調整するなり固定値にするなり確認が必要かな。まあ、判定部分以外の細かい処理内容についてはその時々の要件に応じてということで。
また、今回は3点以上のタッチは無視して捨てていますが、頑張れば3本指、4本指のジェスチャーについても処理を分けることもできそう。(今のとこ必要ないからやらないけど)
以上、2本指操作の判定方法でした。
Kivy自体に複数点のタッチ操作のための仕組みが用意されているような気がして探してみたのですが、使えそうな情報が見つけられず、ちょっと無理やり感のある実装をしました。
こんなことしなくても実装できるのに・・・という情報あれば教えてもらえると嬉しいです。
コメント