2010/11/28

OnGestureListener の処理順

Android の Activity には onTouchEvent というのがあって、シングルタップを捕まえることができます。しかし、ダブルタップのような複雑なイベントを捕まえる機能はデフォルトでは備わっていません。

そういった複雑な動作を捕まえるためには、Activity に OnGestureListener, OnDoubleTapListener というインターフェースを実装してやります。
このとき、どのイベントがどのタイミングで呼ばれるのか調べるために、以下のようなコードを書いてみました。

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;
import android.view.GestureDetector;
import android.view.MotionEvent;

public class Main extends Activity implements OnGestureListener, OnDoubleTapListener {

    private GestureDetector gestureDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        gestureDetector = new GestureDetector(this, this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        super.dispatchTouchEvent(e);
        gestureDetector.onTouchEvent(e);
        return onTouchEvent(e);
    }

    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.i("test", "onSingleTapConfirmed");
        return false;
    }

    public boolean onDoubleTap(MotionEvent e) {
        Log.i("test", "onDoubleTap");
        return false;
    }

    public boolean onDoubleTapEvent(MotionEvent e) {
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("test", "onDoubleTapEvent DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("test", "onDoubleTapEvent MOVE");
            break;
        case MotionEvent.ACTION_UP:
            Log.i("test", "onDoubleTapEvent UP");
            break;
        default:
            Log.i("test", "onDoubleTapEvent OTHER");
            break;
        }
        return false;
    }

    public boolean onDown(MotionEvent e) {
        Log.i("test", "onDown");
        return false;
    }

    public void onShowPress(MotionEvent e) {
        Log.i("test", "onShowPress");
    }

    public boolean onSingleTapUp(MotionEvent e) {
        Log.i("test", "onSingleTapUp");
        return false;
    }

    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
            float distanceY) {
        Log.i("test", "onScroll");
        return false;
    }

    public void onLongPress(MotionEvent e) {
        Log.i("test", "onLongPress");

    }

    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
            float velocityY) {
        Log.i("test", "onFling");
        return false;
    }
}

Xperia (Android 2.1) で試した結果、以下のようになりました。

2010/11/27

onDoubleTap と onDoubleTapEvent の違い

Android の Activity には onTouchEvent というのがあって、シングルタップを捕まえることができます。しかし、ダブルタップのような複雑なイベントを捕まえる機能はデフォルトでは備わっていません。

そういった複雑な動作を捕まえるためには、Activity に OnGestureListener, OnDoubleTapListener というインターフェースを実装してやります。
(具体的な使い方は Google Map でダブルタップズーム for Android を参考にしてください)

ところで、 OnDoubleTapListener には OnDoubleTap と OnDoubleTapEvent という2つのメソッドが存在しますが、最初どういう違いがあるのか良くわからなかったので、ちょっと調べてみました。
って言っても、コメント読んだだけですが :-)


OnDoubleTap の ToolTip には、
Notified when a double-tap occurs.
と書いてあります。直訳すると、
ダブルタップされた時に通知されます
って感じでしょうか。そのままですね。


OnDbouleTapEvent の ToolTip には、
Notified when an event within a double-tap gesture occurs, including the down, move, and up events.
と書いてあります。
ダブルタップ中にイベントがおこると通知されます。ダウン、移動、アップを含みます
という感じでしょうか。
実際、OnDbouleTapEvent をログで捕まえると、一回のダブルタップで4,5回のイベントを捕まえる事ができます。
OnDbouleTapEvent が呼ばれた起因を調べるには、以下のように MotionEvent の getAction メソッドを使えば可能です。
public boolean onDoubleTapEvent(MotionEvent e) {
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("Test", "Action Down.");
            return true;
        case MotionEvent.ACTION_UP:
            Log.i("Test", "Action Up.");
            return true;
        case MotionEvent.ACTION_MOVE:
            Log.i("Test", "Action Move.");
            return true;
        default:
            Log.i("Test", "Action Other.");
            return true;
        }
    }
2回タップした後に、指を離さず、ちょっと動かしてみたときのログは以下のようになりました。
11-26 23:43:57.286: INFO/Test(326): Action Down.
11-26 23:43:57.406: INFO/Test(326): Action Move.
11-26 23:43:57.447: INFO/Test(326): Action Move.
11-26 23:43:57.476: INFO/Test(326): Action Move.
11-26 23:43:57.536: INFO/Test(326): Action Move.
11-26 23:43:57.576: INFO/Test(326): Action Move.
11-26 23:43:57.607: INFO/Test(326): Action Move.
11-26 23:43:57.827: INFO/Test(326): Action Up.

2010/11/28 追記
その他のイベントも、どのような順番で処理されるのか調べてみました。
OnGestureListener の処理順
2010/11/20

Google Maps でダブルタップズーム for Android

Android で Map を扱うアプリを作っていると、デフォルトで入っている Google Map のようにダブルタップでズームして欲しいと思います。
ところが、なかなかこれが難しいのです。
調べてみると、皆さんなかなか苦労しているようです。

Android: MapActivityでダブルタップする (パトラッシュさん)

そこで、もう少しシンプルなやり方が無いか考えてみました。
ポイントは、onDoubleTapEvent ではなく onDoubleTap を使うことと、ZoomControl を連続タップした時にダブルタップとして認識されないようにする方法です。

import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;

import android.graphics.Rect;
import android.os.Bundle;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.GestureDetector.OnDoubleTapListener;
import android.view.GestureDetector.OnGestureListener;

public class Main extends MapActivity implements OnGestureListener, OnDoubleTapListener {
    private MapView mapView;
    private GestureDetector gestureDetector;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mapView = (MapView)findViewById(R.id.mapView01);
        mapView.setBuiltInZoomControls(true);

        gestureDetector = new GestureDetector(this, this);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent e) {
        super.dispatchTouchEvent(e);

        // ZoomController の 画面上の座標を取得する
        int[] offset = new int[2];
        mapView.getZoomButtonsController().getContainer().getLocationOnScreen(offset);
        // ZoomController 中の Hit領域の矩形を取得する
        // (Hitとは領域は + - が表示されている領域のこと)
        Rect rect = new Rect();
        mapView.getZoomButtonsController().getZoomControls().getHitRect(rect);
        // 画面上の座標に変換
        rect.offset(offset[0], offset[1]);
        if (!rect.contains((int)e.getRawX(), (int)e.getRawY())) {
            // タッチされた場所が、Hit領域に含まれていなければ
            gestureDetector.onTouchEvent(e);
        }

        return onTouchEvent(e);
    }

    public boolean onDoubleTap(MotionEvent e) {
        // ズーム処理
        mapView.getController().zoomInFixing((int)e.getX(), (int)e.getY());
        return true;
    }

    // ・・・
    // その他の 自動生成されたメソッド・スタブ に関しては省略

}

以下、簡単な解説です。
2010/11/04

ローカルに保存しちゃったメールを Gmail にインポートする (続き)

先日、ローカルに保存しちゃったメールを Gmail にインポートする (送受信日はそのまま) という記事を書いたのだけれど、やっぱり皆さん考えることは同じのようで、同じような事をしているサイトを見つけてしまいました。かつ、もっと簡単に…

GMailにメールを完全移行する(http://gmail.1o4.jp/import.html)

確かに、ローカルのメールをインポートできるメールサービスを使えば簡単に出来るよなぁ…
もっとちゃんと検索してからやればよかった(笑)

しかし、この「Gmailの使い方!」ってサイトすごいなぁ。
2010/11/02

ローカルに保存しちゃったメールを Gmail にインポートする (送受信日はそのまま)



11/4 追記 → http://kokufu.blogspot.com/2010/11/gmail_04.html


最近、スパムメールの量が一気に増えました。
スパムフィルタはかけているので、別に構わないと言えば構わないのですが、以前から Gmail に移行しようと思っていたところだったので、ちょうど良い機会だから乗り換えることにしました。

ところが、Gmail はローカルに保存しちゃったメールをインポートする術を提供してくれてないようなのです。(2010年10月現在)
しかし、今までのメールを破棄はしたくはないのです。
調べてみると Google Mail Loader (http://www.marklyon.org/gmail/) を使うと既存のメールを Gmail に送ることができるということがわかりました。しかし、メールの送受信日時が現在になってしまうという問題があるのです。出来れば、送受信日時はオリジナルを維持して欲しいものです。

そこで、ローカルに保存しちゃったメールを、もう一回サーバに上げてインポートさせることで、送受信日時を維持したままGmailにアップすることにしました。
以下、その手順を簡単にまとめますが、結構面倒くさいです。
自分でサーバを建てられる人以外にはお勧めしません。ご注意ください。

私は VMWare上に Windows XP をインストールし、Mercury Mail をメールサーバとして使用しましたが、他のメールサーバでも大丈夫だと思います。(保証はしかねますが)

  1. VMWare を準備する
    本質じゃないので割愛
    ネットワーク接続はブリッジ、物理ネットワーク接続の状態を複製するのチェックボックスを外しておくと、VMWare上のOSに個別のIPアドレスが割り振られるので、設定が楽になります。

  2. Mercury Mail インストール
    以下をインストールしました。
    Mercury/32 Mail Transport System for Win32 and NetWare Systems v4.72
    http://www.pmail.com/

    設定方法などは、以下のサイトなどに詳しく載ってます。
    基本的には、SMTP と POP3 サーバーが動いていればオッケーなので、プラグインなどはインストールしなくても良いです。
    http://mizushima.ne.jp/Windows/Mail/Mercury/Mercury.php
    http://www.aconus.com/~oyaji/mail2/mercurymail.htm

  3. 110番と25番のポートを開けます
    ファイヤーウォールの設定でポートを開けます
    私は VMWare 上に新規の環境を作ったので、ファイヤーウォール無効にしてしまいました。そっちの方が手っ取り早かったので。あまりお勧めはしません。

    ルータを使っている場合は、ルータのポート転送設定もしてやる必要があります。

    ここまで来たら、メールサーバとして動作するはずですので、一度、外部のメーラーからアクセスしてみて、メールサーバとして動作するか確認してみると良いでしょう。

  4. 既存のメールを eml ファイルとしてエクスポート
    Mercury Mail は EML形式でファイルを管理しているようです。
    大抵のメーラーは eml ファイルをエクスポートする機能がついていると思うので、それを利用して、emlを吐き出します。
    UNIX系のメールサーバはmbox形式かもしれません。

  5. ファイル名を変換する
    Mercury Mail は eml ファイルを .cnm ファイルとして持つ仕様のようなので、拡張子 .eml を .cnm に変換します。
    かつ、Mercury Mail は日本語ファイル名が使えません。ファイル名を半角英数字に変換する必要もあります。
    参考までに、私が作ったスクリプトを挙げておきます。
    #!/bin/sh
    
    j=0
    for i in *.eml
    do
        echo "$i" "${j}.CNM"
        mv "$i" "${j}.CNM"
        j=`expr $j + 1`
    done

  6. サーバにアップする
    Mercury Mail の場合、C:\MERCURY\MAIL\username\ に先ほど作成した xxxx.CNM をコピーすればオッケーです。

  7. Gmail にインポート
    gmail の 「アカウントとインポート」「メッセージと連絡先のインポート」からインポート情報を入れます



    個人的には、インポートしたメールには新規のラベルをつけておいた方が良いと思います。
    ラベルを外すのは簡単ですが、つけるのは意外と面倒です。
    インポート開始を押せば、メールの取り込みが始まります。トラフィックに負担をかけないためだと思いますが、ゆっくり取り込みます。気長に待ちましょう。