Androidでマルチタッチを使う

# はじめに
Androidでマルチタッチを使う方法を記載します。

# タッチイベントの取得
タッチパネルを触るとonTouchEvent()がコールされます。
Activity or Viewでこの関数をoverrideすることでイベントを取得できます。
イベントはonTouchEvent()の引数MotionEventに格納されます。

イベントには種別があり、これをアクションといいます。
アクションはgetActionMasked()で取得します。

# アクションの種類
アクションには次のものがあります。

タッチしていない状態でタッチしたとき

- ACTION_DOWN
- 0x00000000

タッチしている状態でタッチしなくなったとき

- ACTION_UP
- 0x00000001

すでにタッチしている状態で追加タッチしたとき
例えばある指でタッチしている状態で、別の指でタッチしたとき

- ACTION_POINTER_DOWN
- 0x00000005

複数の指でタッチしている状態で、そのうち1つの指を離したときのイベント

- ACTION_POINTER_UP
- 0x00000006

タッチ中に指を動かした場合

- ACTION_MOVE
- 0x00000002

何らかの要因でキャンセルされた場合、ACTION_UPと同じ扱いにした方がいい。

- ACTION_CANCEL
- 0x00000003

# 複数タッチの識別方法

## Pointer indexとData index
複数のタッチがあるときに、それぞれのタッチを識別するために、
タッチには識別子**Pointer index**がついています。
タッチ開始時(触った時)に付与されて、タッチ終了時(離したとき)になくなります。

一方でMotionEvent内に格納されたデータには配列のindexがあります。
これを**Data index**ということにします。

少しわかりにくいので例で説明します。

例:
![image](https://qiita-image-store.s3.amazonaws.com/0/84205/f8e8ad3e-725b-3c59-1103-9a9a48233026.png)

Data indexは配列のindexなので、0から始まります、1つずつ増加して、(Data配列サイズ-1)で終わります。
Pointer indexはタッチの識別子です。複数の指で触ったり、離したりすることを考えると、順番はバラバラになることがあります。
また、値が(Data配列サイズ-1)を超える場合もあります。x,yは各タッチの座標値です。

## Pointer index, 座標値の取得
Pointer index,座標値の取得方法は次の通りです。

```java
// 注意 : 説明のための必要最小限のソースコードに切り出しているため、実行はできません。
MotionEvent event;

int count = event.getPointerCount();

for (int i=0; i<count; i++) {
    int pid = event.getPointerId(i);
    float x = event.getX(i);
    float y = event.getY(i);
}
```

タッチしている数(Data配列のサイズ)をgetPointerCount()で取得します。Data indexでループします。
Pointer indexは引数をData indexとしてgetPointerId()で取得します。
座標値はgetX(),getY()で取得します。

## Pointer indexを指定してData indexを取得する
あるPointer indexが、どのData indexに対応しているかを知るにはfindPointerIndex(Pointer index)を使います。
引数にPointer indexを指定すると戻り値にData indexが返ってきます。

# 蓄積されたイベント
処理負荷が増加すると、あるonTouchEvent()と次のonTouchEvent()の間に、
複数回タッチイベントが発生することがあります。下図参照。

例:

![image](https://qiita-image-store.s3.amazonaws.com/0/84205/dee91339-27df-3274-1818-1bdf733541d6.png)

この時、複数のタッチイベントはMotionEvent内に蓄積されます。
後ろ側のonTouchEventで蓄積された(例ではA)タッチイベントと現在のタッチイベント(例ではB)が取得できます。

蓄積されたタッチイベントの個数はgetHistorySize()で取得できます。
例ではタッチイベントAが1つ蓄積されるので値1が取得されます。

蓄積された座標データはgetHistoricalX(Data index, index), getHistoricalY(Data index, index)で取得できます。
第2引数は0~(getHistorySize()-1)の値です。index値が小さいほど古いデータになります。

```java
@Override
public boolean onTouchEvent(MotionEvent event) {
    int count = event.getPointerCount();

    for (int i=0; i<count; i++) {
        float x = event.getX(i);
        float y = event.getY(i);
        int hsize = event.getHistorySize();

        for (int j=0; j<hsize; j++) {
            float hx = event.getHistoricalX(i, j);
            float hy = event.getHistoricalY(i, j);
        }
    }

    return true;
}
```

# サンプルソースコード

```java
@Override
public boolean onTouchEvent(MotionEvent event) {
    int action1 = event.getAction();
    int action2 = event.getActionMasked();
    int count = event.getPointerCount();

    Log.e("", "getAction=0x" + String.format("%04x", action1) + ",getActionMasked=0x" + String.format("%04x", action2)
        + ",getPointerCount=" + count);

    for (int i=0; i<count; i++) {
        int pid = event.getPointerId(i);
        int id = event.findPointerIndex(pid);
        if (id == -1)
            continue;
        float x = event.getX(id);
        float y = event.getY(id);
        float p = event.getPressure(id);
        float s = event.getSize(id);
        int hsize = event.getHistorySize();

        String line = "\t";
        line += "getPointerId=" + pid + ",";
        line += "getHistorySize=" + hsize + ",";

        for (int j=0; j<hsize; j++) {
            float hx = event.getHistoricalX(id, j);
            float hy = event.getHistoricalY(id, j);
            float hp = event.getHistoricalPressure(id, j);
            float hs = event.getHistoricalSize(id, j);
            line += "x=" + String.format("%.2f", hx) + ",";
            line += "y=" + String.format("%.2f", hy) + ",";
            line += "p=" + String.format("%.4f", hp) + ",";
            line += "s=" + String.format("%.4f", hs) + ",";
        }

        line += "x=" + String.format("%.2f", x) + ",";
        line += "y=" + String.format("%.2f", y) + ",";
        line += "p=" + String.format("%.4f", p) + ",";
        line += "s=" + String.format("%.4f", s) + ",";

        Log.e("", line);
    }
    
    return true;
    //return super.onTouchEvent(event);
}
```

# Appendix
## getAction()とgetActionMasked()の違い
getAction()とgetActionMasked()の違いを説明します。

シングルタッチの場合、両者で取得されるイベントは同じになります。
マルチタッチの場合、違いがあります。
getAction()で取得されるイベントにはPointer indexが付加されます。
getActionMasked()で取得されるイベントはPointer indexがマスクされたものです。

具体例で説明します。

シングルタッチでタップしたときのアクションです。
双方同じ値が取得されます。

```
D/: getAction=0x0000,getActionMasked=0x0000
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0001,getActionMasked=0x0001
```

マルチタッチの例として、
1本目の指でタッチ後、2本目の指でタッチし、指2→指1の順番で指を離したときのアクションで説明します。

2本目をタッチしたときと、2本目を離したとき(*箇所)、
getAction()とgetActionMasked()で取得されるアクションに違いがあることが分かります。
getAction()で取得されるアクションにはPointer indexも一緒に渡されます。
具体的には2本目の指をタッチしたとき、アクションは0x0105となり0x01の部分は2本目を表します。
getActionMasked()が返すアクションはPointer indexがマスクされたものになります。

```
D/: getAction=0x0000,getActionMasked=0x0000
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0105,getActionMasked=0x0005(*)
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0002,getActionMasked=0x0002
D/: getAction=0x0106,getActionMasked=0x0006(*)
D/: getAction=0x0001,getActionMasked=0x0001
```