マルチタップ操作の処理

Compose をお試しください
Jetpack Compose は、Android で推奨される UI ツールキットです。Compose でタッチと入力を使用する方法を学びましょう。

マルチタップ操作とは、複数のポインタ(指)で同時に画面をタップする場合のことです。このドキュメントでは、複数のポインタが含まれる操作を検出する方法について説明します。

複数のポインタのトラッキング

複数のポインタで同時に画面をタップすると、システムは次のタッチイベントを生成します。

  • ACTION_DOWN: 最初のポインタが画面をタップしたときに送信されます。これで操作が開始されます。このポインタのポインタデータは、常に MotionEvent のインデックス 0 にあります。
  • ACTION_POINTER_DOWN: 最初のポインタの後に画面に入った追加のポインタに対して送信されます。`getActionIndex()` を使用して、離れたポインタのインデックスを取得できます。
  • ACTION_MOVE: 任意の数の ポインタを含む操作で変更が発生したときに送信されます。
  • ACTION_POINTER_UP: 主要でないポインタが離れると送信されます。 `getActionIndex()` を使用して、離れたポインタのインデックスを取得できます。
  • ACTION_UP: 最後のポインタが画面を離れると送信されます。
  • ACTION_CANCEL: すべてのポインタを含む操作全体がキャンセルされたことを示します。

操作の開始と終了

操作は、ACTION_DOWN イベントで始まり、ACTION_UP イベントまたは ACTION_CANCEL イベントで終わる一連のイベントです。一度にアクティブな操作は 1 つです。DOWN、MOVE、UP、CANCEL のアクションは、操作全体に適用されます。たとえば、ACTION_MOVE を含むイベントは、その瞬間にすべてのポインタが移動したことを示す場合があります。

ポインタのトラッキング

ポインタのインデックスと ID を使用して、MotionEvent 内の個々のポインタの位置をトラッキングします。

  • インデックス: MotionEvent は、ポインタ 情報を配列に保存します。ポインタのインデックスは、この 配列内での位置です。ほとんどの MotionEvent メソッドは、ポインタ ID ではなくパラメータとしてポインタ インデックスを取得します。
  • ID: 各ポインタには ID マッピングもあり、これはタッチイベント全体で 持続して、操作の全体で個々のポインタを トラッキングできるようにします。

モーション イベント内で個々のポインタが表示される順序は定義されていません。したがって、 ポインタのインデックスはイベントごとに変わる可能性がありますが、ポインタのポインタ ID は、ポインタがアクティブである限り一定であることが保証されます。 getPointerId() メソッドを使用してポインタの ID を取得し、操作での後続のモーション イベントすべてでポインタをトラッキングします。その後、連続するモーション イベントについては、findPointerIndex() メソッドを使用して、そのモーション イベントの特定のポインタ ID のポインタ インデックスを取得します。 例:

Kotlin

private var mActivePointerId: Int = 0

override fun onTouchEvent(event: MotionEvent): Boolean {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0)

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    val (x: Float, y: Float) = event.findPointerIndex(mActivePointerId).let { pointerIndex ->
        // Get the pointer's current position.
        event.getX(pointerIndex) to event.getY(pointerIndex)
    }
    ...
}

Java

private int mActivePointerId;

public boolean onTouchEvent(MotionEvent event) {
    ...
    // Get the pointer ID.
    mActivePointerId = event.getPointerId(0);

    // ... Many touch events later...

    // Use the pointer ID to find the index of the active pointer
    // and fetch its position.
    int pointerIndex = event.findPointerIndex(mActivePointerId);
    // Get the pointer's current position.
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    ...
}

複数のタッチポインタをサポートするには、個々の ACTION_POINTER_DOWN イベントと ACTION_DOWN イベントの時刻に、すべてのアクティブなポインタを ID とともにキャッシュに保存します。ACTION_POINTER_UP イベントと ACTION_UP イベントで、キャッシュからポインタを削除します。これらのキャッシュに保存された ID は、その他のアクション イベントを正しく処理するのに役立ちます。たとえば、ACTION_MOVE イベントを処理するときに、キャッシュに保存されたアクティブなポインタ ID ごとにインデックスを見つけ、getX() 関数と getY() 関数を使用してポインタの座標を取得し、これらの座標をキャッシュに保存された座標と比較して、どのポインタが移動したかを検出します。

getActionIndex() 関数は、ACTION_POINTER_UP イベントと ACTION_POINTER_DOWN イベントでのみ使用してください。この関数は ACTION_MOVE イベントでは使用しないでください。常に 0 を返します。

MotionEvent アクションを取得する

MotionEvent のアクションを取得するには、 getActionMasked() メソッドまたは互換バージョンの MotionEventCompat.getActionMasked() を使用します。以前の getAction() メソッドと異なり、getActionMasked() は複数のポインタで動作するように設計されています。ポインタ インデックスを含めずにアクションを返します。有効なポインタ インデックスを持つアクションの場合は、次のスニペットに示すように、getActionIndex() を使用して、アクションに関連付けられたポインタのインデックスを返します。

Kotlin

val (xPos: Int, yPos: Int) = MotionEventCompat.getActionMasked(event).let { action ->
    Log.d(DEBUG_TAG, "The action is ${actionToString(action)}")
    // Get the index of the pointer associated with the action.
    MotionEventCompat.getActionIndex(event).let { index ->
        // The coordinates of the current screen contact, relative to
        // the responding View or Activity.
        MotionEventCompat.getX(event, index).toInt() to MotionEventCompat.getY(event, index).toInt()
    }
}

if (event.pointerCount > 1) {
    Log.d(DEBUG_TAG, "Multitouch event")

} else {
    // Single touch event.
    Log.d(DEBUG_TAG, "Single touch event")
}

...

// Given an action int, returns a string description.
fun actionToString(action: Int): String {
    return when (action) {
        MotionEvent.ACTION_DOWN -> "Down"
        MotionEvent.ACTION_MOVE -> "Move"
        MotionEvent.ACTION_POINTER_DOWN -> "Pointer Down"
        MotionEvent.ACTION_UP -> "Up"
        MotionEvent.ACTION_POINTER_UP -> "Pointer Up"
        MotionEvent.ACTION_OUTSIDE -> "Outside"
        MotionEvent.ACTION_CANCEL -> "Cancel"
        else -> ""
    }
}

Java

int action = MotionEventCompat.getActionMasked(event);
// Get the index of the pointer associated with the action.
int index = MotionEventCompat.getActionIndex(event);
int xPos = -1;
int yPos = -1;

Log.d(DEBUG_TAG,"The action is " + actionToString(action));

if (event.getPointerCount() > 1) {
    Log.d(DEBUG_TAG,"Multitouch event");
    // The coordinates of the current screen contact, relative to
    // the responding View or Activity.
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);

} else {
    // Single touch event.
    Log.d(DEBUG_TAG,"Single touch event");
    xPos = (int)MotionEventCompat.getX(event, index);
    yPos = (int)MotionEventCompat.getY(event, index);
}
...

// Given an action int, returns a string description
public static String actionToString(int action) {
    switch (action) {

        case MotionEvent.ACTION_DOWN: return "Down";
	case MotionEvent.ACTION_MOVE: return "Move";
	case MotionEvent.ACTION_POINTER_DOWN: return "Pointer Down";
	case MotionEvent.ACTION_UP: return "Up";
	case MotionEvent.ACTION_POINTER_UP: return "Pointer Up";
	case MotionEvent.ACTION_OUTSIDE: return "Outside";
	case MotionEvent.ACTION_CANCEL: return "Cancel";
    }
    return "";
}
図 1. マルチタッチの描画 パターン。

参考情報

入力イベントの詳細については、次のリファレンスをご覧ください。