真 もわ爛漫

しゃーら、しゃーらしゃーら

Multi Touch を使ってみる。

取り敢えずコード貼る。ちなみに用途によってはバグになる挙動があるので注意。

/** Apache License Nanodesu */
package jp.mowanet.nanodesu.only.nanodesumultitouchexp;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.TextView;

public class NanodesuMultiTouchExp extends Activity {
    private boolean mFirstPressed = false;
    private float mFirstX;
    private float mFirstY;
    private boolean mSecondPressed = false;
    private float mSecondX;
    private float mSecondY;

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

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        final int pointerCount = event.getPointerCount();  
        // Just in case. Return when there's no multiple pointer support.
        if (pointerCount < 1) {
            return false;
        }
        
        final int eventCode = event.getAction();
        final int historyLength = event.getHistorySize() / pointerCount;
        final int firstPointerIndex = event.findPointerIndex(0);
        final int secondPointerIndex = (pointerCount > 1 ? event.findPointerIndex(1) : -1);
        if (historyLength > 0) {
            for (int i = 0; i < historyLength; i++) {
                {
                    final float firstX = event.getHistoricalX(firstPointerIndex, i);
                    final float firstY = event.getHistoricalY(firstPointerIndex, i);
                    handleMoveEvent(eventCode, firstX, firstY);
                }
                if (pointerCount > 1) {
                    final float secondX = event.getHistoricalX(secondPointerIndex, i);
                    final float secondY = event.getHistoricalY(secondPointerIndex, i);
                    handleMoveEvent(eventCode, secondX, secondY);
                }
            }
        } else {
            // Seems we cannot use even  event.getX(firstPointeIndex).
            handleMoveEvent(eventCode, event.getX(), event.getY());
        }

        TextView textView = (TextView)findViewById(R.id.text);
        textView.setText(
                "0: (X, Y) = (" + mFirstX + ", " + mFirstY + ")\n" +
                "1: (X, Y) = (" + mSecondX + ", " + mSecondY + ")\n");
        return true;
    }

    private void handleMoveEvent(int eventCode, float x, float y) {
        if (((eventCode & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN) ||
                ((eventCode & MotionEvent.ACTION_POINTER_1_DOWN)
                        == MotionEvent.ACTION_POINTER_1_DOWN)) {
            if (!mFirstPressed) {
                mFirstX = x;
                mFirstY = y;
                mFirstPressed = true;
            } else if (!mSecondPressed){
                mSecondX = x;
                mSecondY = y;
                mSecondPressed = true;
            } else {
                Log.e("@@@", "Unknown event.");
            }
        } else if ((eventCode & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_UP ||
                ((eventCode & MotionEvent.ACTION_POINTER_1_UP)
                        == MotionEvent.ACTION_POINTER_1_UP)) {
            if (mFirstPressed && (!mSecondPressed || nearToFirst(x, y))) { 
                mFirstPressed = false;
            } else if (mSecondPressed) {
                mSecondPressed = false;
            } else {
                Log.e("@@@", "Unknown event");
            }
        } else if ((eventCode & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_MOVE) {
            if (nearToFirst(x, y)) {
                mFirstX = x;
                mFirstY = y;
            } else {
                mSecondX = x;
                mSecondY = y;
            }
        } else {
            Log.d("@@@", "unknown");
        }
    }

    private boolean nearToFirst(float tmpX, float tmpY) {
        float distanceFirst = (((tmpX - mFirstX)*(tmpX - mFirstX))
                + ((tmpY - mFirstY)*(tmpY - mFirstY)));
        float distanceSecond = (((tmpX - mSecondX)*(tmpX - mSecondX))
                + ((tmpY - mSecondY)*(tmpY - mSecondY)));
        return distanceFirst <= distanceSecond;
    }
}

ドキュメントにはいつgetHistoricalX()とかを使っていつgetX()を使うべきかがなんも書いてない気がするんだけど、見た感じだとNexus OneではACTION_DOWNとACTION_UPではhistoryが長さ0になってgetX()とかを使わざるを得ず、MOVEではindexが常に0になる気がしたので距離を測る。確かに二点が同時に動いたときにどっちの動きのイベントがどっちの点に対応していたかを自動認識してくれるのはむしろうざいかもしれんから自分で距離測るのが良いかな、という。片方を離してもう一度押すとfirstとsecondがひっくり変えるし、結局なんか変なコードになった。

上記のコードだと、firstの指を離してsecondの指をもともとfirstの指があった位置辺りにうごかすとやっぱり入れ替わる。多分、離した後の次のMOVEのイベント時に何かコードを入れればその挙動もほぼ無くなるはず。二点の入れ替わりがどうでも良い場合は上記のコードよりもっとラフに書けるかもしれないし、つまり何か抽象化するよりは全部面倒見た方が楽そうだね、という感じ。

意識としてはカード2枚を同時に前に突き出す感じで、三国志大戦みたいに

追記

参考にしたソースコードは以下のページにある

My Android multi-touch controller, ported to Eclair | Luke Hutchison

マーケットで「Multi Touch」とか検索するとそれっぽいテストアプリが出てくるのでコード書く気しねーよ && 俺のデバイス2.1だぜひゃっほう、という人は試してみるのをオススメする。画像が出てくるので上の奴よりわかりやすい。

マルチタッチになるとリアルデバイスでやる以外にテスト出来ないってのが、むしろいいよね(酷