• スポンサードリンク

イメージの拡大縮小・移動・回転できるImageViewを作ってみた〜その3〜タッチ操作編

Android ImageView widget

イメージの拡大縮小・移動・回転はできるようになったので、今回はタッチによって操作をする方法について書きます。

まずはZoomImageViewの操作方法を決めます。本当は一番最初に決めてたんだけどね(^_^;)

  1. 1本指でタッチ&ドラッグすると、イメージを移動する。
  2. 2本指(以上)でタッチ&ピンチすると、イメージを拡大縮小する。
  3. 2本指でタッチ&ホールド(動かさずに長押し)してからタッチ位置を回すように動かすと、イメージを回転する。回転可能になるとフィードバックのために、表示しているイメージの色を一定時間反転させる
  4. 1本指でタッチ&ホールド(動かさずに長押し)すると、拡大縮小・移動・回転をリセットして最初に表示した状態に戻す。やっぱり元に戻せないと不便ですからね。

この操作方法からZoomImageViewの取るべき状態は、

  1. 何も操作をしていない
  2. イメージの移動操作中
  3. イメージの拡大縮小操作中
  4. イメージの回転操作中

の少なくとも4状態が有るのがわかりますね。それぞれの状態を表す定数を定義しちゃいましょう。

数字の1と3が抜けてますね。なぜでしょう?
実は上の4つの状態以外にも中間状態を存在させた方が処理が少し楽になるのです。

  1. 1つ目のタッチイベントが来たけどシングルタッチなのかマルチタッチなのか長押しなのかそのままドラッグするのかわからない状態、ユーザーの操作待ちの状態
  2. マルチタッチイベントが来たけどピンチ操作で拡大縮小するのか長押しして回転させたいのかわからない状態、ユーザーの操作待ちの状態

の2つの状態を追加しましょう。

もっと細かく分けることも可能ですが細かすぎるのも面倒になるだけですので、今回は初期化のための-1を加えた7状態を使うことにします。

次にどの状態の時にどんなイベントが起きると状態遷移が起こるかをまとめてみましょう。

状態遷移表

状態#init呼び出しタッチ操作開始マルチタッチ操作開始タッチ位置が変化したキャンセルした指を離したマルチタッチで指を離した長押し時間経過
不定(-1)STATE_NONへ------------------------
STATE_NONSTATE_NONへSTATE_WAITINGへ------------------
STATE_WAITINGSTATE_NONへ(STATE_WAITINGへ)STATE_WAITING解除
STATE_CHECKINGへ
STATE_WAITING解除
STATE_DRAGINGへ
STATE_WAITING解除
STATE_NONへ
STATE_WAITING解除
STATE_NONへ
STATE_NONへRESET実行
STATE_NONへ
STATE_DRAGINGSTATE_NONへ(STATE_WAITINGへ)STATE_CHECKINGへ移動実行STATE_NONへSTATE_NONへSTATE_NONへ---
STATE_CHECKINGSTATE_NONへ(STATE_WAITINGへ)---STATE_ZOOMINGへSTATE_NONへSTATE_NONへSTATE_NONへSTATE_ROTATINGへ
STATE_ZOOMINGSTATE_NONへ(STATE_WAITINGへ)---拡大縮小実行STATE_NONへSTATE_NONへSTATE_NONへ---
STATE_ROTATINGSTATE_NONへ(STATE_WAITINGへ)---回転実行STATE_NONへSTATE_NONへSTATE_NONへ---
MotionEvent---ACTION_DOWNACTION_POINTER_DOWNACTION_MOVEACTION_CANCELACTION_UPACTION_POINTER_UP

こういう風に状態とイベント・アクションを表としてまとめたものを状態遷移表と呼んだりします。

今回の操作方法を実現する方法は色々あります。もちろんGestureDetectorを使ってもいいんですが、状態遷移がわかりにくくなってしまうので今回はonTouchEvent内で自前で実装します。

まずは、他のアプリ等と操作感を一致させるために、長押し時間を端末の設定から取得するようにします。

これでCHECK_TIMEOUT定数に長押し時間が取得出来ました。この値は例えばGestureDetectorを使った時と同じになります。

続いてユーザーが画面にタッチした時のイベント処理を行うメソッド、onTouchEventを載せます。

一部状態遷移表に載ってない処理があったりしますが、概ね状態遷移表に従っているのがわかりましたか?簡単ですね(行に色を付けているところが発生したイベントです)。

ところで、ところどころで謎のメソッドView#removeCallbacksを呼び出しているに気づきましたか?

これが長押しを検出するための方法の1つなんです。
皆さんは、長押しの検出ってどうすると思いましたか?例えば、最初にタッチした時の時間を保存しておいて、次のイベントが来た時に時間を比較して、長押しかどうかを判定しようと考えた人もいるのでは無いでしょうか。
でも、実はonTouchEventはタッチ状態が変化した時、つまりタッチした時、タッチ位置が変化した時、タッチをやめた時などにしかイベントが発生しません。
つまり長押ししているだけではどんなに時間がたってもonTouchEventは来ないのです(GestureDetectorを使っていれば、今回の長押しの検出方法と同様の方法でonLongPressコールバックを呼び出してくれます)。
そこでどうするかというと、最初のACTION_DOWN(シングルタッチを開始した時)やACTION_POINTER_DOWN(マルチタッチを開始した時)に、一定時間後に実行してもらうようにお願いするのです。

例えば#startWaitingではこんな風にお願いしています。

ここで出てきたView#postDelayedにRunnable(WaitImageResetクラスのインスタンス)と遅延時間(ここではCHECK_TIMEOUT)を渡すことで、一定時間後にWaitImageReset#runが実行されます。また、長押し時間を経過する前に例えば別のイベントが発生して状態遷移する場合には、お願いを取り消す必要が有ります。それがView#removeCallbacksの呼び出しになります。
ちなみに、既に実行間終了してしまっているRunnableインスタンスを引数にしてView#removeCallbacksを呼び出しても無視されるだけでエラーにはなりませんし、同じRunnableインスタンス使って複数回お願いすることも出来ます。

同じように、マルチタッチした際には回転開始かどうかの確認を行います。

こんな感じですね。同じようにRunnableを生成した後View#postDelayedを呼び出しています。

そろそろ疲れてきたので今回はここまで。おつかれさまでした。

全体のソースはこちら(GitHub)

« »

  • スポンサードリンク