飴屋

Androidアプリ/日記12

実機でデバッグ

前回、体重の遷移を折れ線グラフで表示するところまでできたので、今回はグラフの表示内容をタッチ操作で制御してみたいと思います。しかし、PCのエミュレータではマウスで擬似的にタッチするしかありません。マウスと指では操作感も大分変わってくると思いますし、なによりマルチタッチがマウスではエミュレートできません。
そこでそろそろ実機を使ってデバッグをしてみようかなと思い立ちました。

私の使っている端末はHTC Desireですので、他の端末と作法が違う部分があってもわからないのですが、実機デバッグ環境を作るまでにやったことをメモしておきます。

実機の設定

  • 「設定」→「アプリケーション」→「不明な提供元」をチェック
  • 「設定」→「アプリケーション」→「開発」→「USBデバッグ」をチェック

eclipseの設定

  • 「Run」→「Run Configurations...」→「Target」タブ→「Deployment Target Selection Mode」を「Manual」に設定

開発PCの設定

  • Android SDKの「usb_driver」から必要に応じて端末のデバイスドライバをインストール

設定完了

USBで開発PCと実機端末をつないでプロジェクトを実行したら、実行環境を尋ねられたので、「Choose a running Android Device」から開発端末っぽいのを選択すれば、実機でアプリが動きました!今後カメラや多種のセンサーを使った機能を提供するときなどは実機での動作確認が必須ですので、これで安心です。

タッチイベントを受け取ってみる

続いて、グラフ上のタッチ操作を取得してそれに応じてグラフを変化させてみます。
前回、GraphViewというViewを継承したクラス上にグラフを描画していました。ViewクラスにはタッチイベントをハンドリングするonTouchEventメソッドがあるので、そのメソッドを上書きしてやります。

private float rangeWeight;
private float rangeTime;
private float originWeight;
private float originTime;
private float startTime;
private float endTime;
private int touchMode;
private static final int TOUCHMODE_NONE = 1;
private static final int TOUCHMODE_DRAG = 2;
private static final int TOUCHMODE_ZOOM = 3;
private float touchSX;
private float touchSY;
private float touchDistanceX;
private float touchDistanceY;
private float startOriginTime;
private float startOriginWeight;
private float startRangeTime;
private float startRangeWeight;
private static final int OFFSET_X = 10;
private static final int OFFSET_Y = 10;

@Override
public boolean onTouchEvent(MotionEvent event)
{
float width = this.getWidth() - OFFSET_X;
float height = this.getHeight() - OFFSET_Y;
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
if (touchMode == TOUCHMODE_DRAG) break;
touchMode = TOUCHMODE_DRAG;
touchSX = event.getX(0);
touchSY = event.getY(0);
startOriginTime = originTime;
startOriginWeight = originWeight;
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (touchMode == TOUCHMODE_ZOOM) break;
touchMode = TOUCHMODE_ZOOM;
touchDistanceX = 0;
touchDistanceY = 0;
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_POINTER_UP:
touchMode = TOUCHMODE_NONE;
break;
}
if (event.getActionMasked() != MotionEvent.ACTION_MOVE) return true;
switch (touchMode) {
case TOUCHMODE_DRAG:
originTime = startOriginTime - (event.getX(0) - touchSX) / width * rangeTime;
originWeight = startOriginWeight + (event.getY(0) - touchSY) / height * rangeWeight;
break;
case TOUCHMODE_ZOOM:
if (touchDistanceX<1) {
touchDistanceX = Math.abs(event.getX(0)-event.getX(1));
startRangeTime = rangeTime;
} else {
rangeTime = startRangeTime * touchDistanceX / Math.abs(event.getX(0)-event.getX(1));
if (rangeTime>31536000000f) rangeTime = 31536000000f;
if (rangeTime<604800000f) rangeTime = 604800000f;
}
if (touchDistanceY<1) {
touchDistanceY = Math.abs(event.getY(0)-event.getY(1));
startRangeWeight = rangeWeight;
} else {
rangeWeight = startRangeWeight * touchDistanceY / Math.abs(event.getY(0)-event.getY(1));
if (rangeWeight<1) rangeWeight = 1;
if (rangeWeight>500) rangeWeight = 500;
}
break;
}
if (originTime+rangeTime > endTime) originTime = endTime-rangeTime;
else if (originTime+rangeTime<endTime && originTime<startTime) originTime = endTime - rangeTime;
if (originWeight<0) originWeight = 0;
if (originWeight+rangeWeight>9999) originWeight = 9999 - rangeWeight;

invalidate();
return true;
}

今回グラフに対して行う操作は以下の二つです。

  • 指一本でなぞるとそれに追従してグラフが上下左右にスクロールする
  • 指二本でつまんだり開いたりすると、二点間の距離の変化に応じてズーミングする

前回、グラフの表示用のパラメータに用意していたrangeWeight、rangeTime、originWeight、originTimeはこの操作を見越して設定していました。スクロール操作は原点の位置を変更すればいいだけですし、ズーミング操作は画面上に表示される値の幅を変更すればいいだけという描画処理になっているので、タッチイベントに応じて上記の4つの変数を変更してあげればよいのです。

いくつか新しく追加した変数もあるので簡単に紹介します。

startTime
最も古い計測日時
endTime
最新の計測日時
touchMode
現在のタッチの状態。TOUCHMODE_NONEならタッチされていない、TOUCHMODE_DRAGならスクロール操作中、TOUCHMODE_ZOOMならズーミング操作中
touchSX
スクロール開始時にタッチされたX座標
touchSY
スクロール開始時にタッチされたY座標
touchDistanceX
ズーミング開始時の指の二点間の距離(X軸)
touchDistanceY
ズーミング開始時の指の二点間の距離(Y軸)
startOriginTime
スクロール開始時の原点位置(X軸)
startOriginWeight
スクロール開始時の原点位置(Y軸)
startRangeTime
ズーミング開始時のX軸の値の幅
startRangeWeight
ズーミング開始時のY軸の値の幅

イベントの種類

onTouchEventには引数にMotionEventオブジェクトが渡されますので、この中身をみてどんなタッチイベントが起こっているかを判断します。今回はMotionEvent.getActionMaskedを使って操作内容を判別していますが、指一本操作だけでよければMotionEvent.getActionで事足りるみたいです。Android OSの進化の中でバージョン2.0のあたりでマルチタッチへの対応が行われたそうなのですが、過去のOSとの互換性を保つためにいろんなメソッドが増えているんでしょうね。
マルチタッチといえば、iPhoneのSafariでピンチズームしてWEBサイトを拡大縮小している様子をみて私は「モバイルはじまったな」と思った口です。今回やっとマルチタッチ処理がかけて幸せでした。

今回使用しているMotionEventのアクションは以下の通りです。

MotionEvent.ACTION_DOWN
一本目の指がタッチ開始
MotionEvent.ACTION_UP
一本目の指のタッチ終了
MotionEvent.ACTION_CANCEL
タッチ処理がキャンセル
MotionEvent.ACTION_MOVE
タッチ中の指が動いた
MotionEvent.ACTION_POINTER_DOWN
二本目の指がタッチ開始
MotionEvent.ACTION_POINTER_UP
二本目の指のタッチ終了

マウス処理と違うのはカーソルの数が複数になることと、触ってないときにMOVEイベントが発生しないことですね。あとはマウスのカーソルと違い、指は太くて厚みのある物体なので、一か所に静止するという操作が難しいことや、操作中に画面が手で隠れてしまいやすいこと、ボタンが小さいと押しにくいことなんかも気をつけたい点ですね。

指の位置

上記のアクションとに加えevent.getX、event.getYで指の位置を確認してタッチパネルに対して何が行われたのかを把握します。引数に0を指定すれば1本目の指の座標を、1を指定すれば2本目の指の座標を取得できます。

マルチタッチに関する情報を集めていたら1本目の座標と2本目の座標が入れ替わる現象が発生することがあるそうです。最新のバージョンでは既に修正されているのかもしれませんが、今回のプログラムでは「二点間の距離」を計測するだけなので、指の位置が入れ替わっても影響はないので詳しくは調べませんでした。

そんなことよりも、実機でマルチタッチ時の座標位置を確認しているとどうも本来の指の位置とは異なる座標が返ってくることがあるような気がしています。これは実機のタッチスクリーンの精度の問題なのか、Androidプラットフォームの問題なのか、原因がまだ判明していません。変な座標が返ってくると自然なズーミングにならないのでちょっと困ってます。

画面の再描画

タッチの状態からもろもろ計算を行い、原点座標の位置や値の表示幅を更新し、値が不正であれば適切に調整を加えてみました。そしたら、グラフを更新するためにinvalidateメソッドを呼び出します。これでできあがりとなります。

Androidのブラウザみたいにスクロール処理に慣性をつけてやったりするとヌルヌル感が増すのかもしれませんし、今回のズーミングだと指先にグラフが追従してくれません。その辺を勝手に処理してくれる便利なクラスが既にあるんじゃないかと思ってますが、探してません。とりあえず、自分で実装できて満足したので、便利なものがあるようならさっさとそれに置き換えたいです。

日記一覧