マルチタッチ フィンガー トラッキング

このトピックでは、複数の指のタッチ イベントを追跡する方法について説明します

マルチタッチ アプリケーションでは、画面上で複数の指が同時に動く場合、個々の指を追跡することが必要な場合があります。 一般的なアプリケーションの 1 つとして、フィンガー ペイント プログラムがあります。 あなたは、ユーザーが 1 つの指で描画できるだけでなく、同時に複数の指で描画できるようにする必要があります。 マルチタッチ イベントを処理する際に、プログラムでは、各指に対応するイベントを区別する必要があります。 Android はこの目的のために ID コードを提供しますが、そのコードの取得と処理に注意が必要な場合があります。

特定の指に関連付けられているすべてのイベントについて、ID コードは変わりません。 ID コードは、指が最初に画面に触れたときに割り当てられ、指が画面から離れると無効になります。 これらの ID コードは、通常、非常に小さい整数であり、Android によってその後のタッチ イベントに再利用されます。

ほとんどの場合、個々の指を追跡するプログラムには、タッチを追跡するための辞書が用意されています。 辞書のキーは、特定の指を識別する ID コードです。 辞書の値は、アプリケーションによって異なります。 FingerPaint サンプルでは、各指のストローク (タッチからリリースまで) が、その指で描かれた線をレンダリングするのに必要である情報をすべて含むオブジェクトに関連付けられます。 プログラムでは、これを目的とした小さな FingerPaintPolyline クラスを定義します。

class FingerPaintPolyline
{
    public FingerPaintPolyline()
    {
        Path = new Path();
    }

    public Color Color { set; get; }

    public float StrokeWidth { set; get; }

    public Path Path { private set; get; }
}

各ポリラインには、色、ストロークの幅、および描画中に線の複数のポイントを蓄積してレンダリングする Android グラフィックス Path オブジェクトが設定されます。

次に示すコードの残りの部分はすべて、FingerPaintCanvasView という名前の、View の派生クラスに含まれています。 このクラスでは、1 つまたは複数の指によってオブジェクトがアクティブに描画されている間、FingerPaintPolyline 型のオブジェクトの辞書を維持します。

Dictionary<int, FingerPaintPolyline> inProgressPolylines = new Dictionary<int, FingerPaintPolyline>();

この辞書を使用すると、ビューは特定の指に関連付けられている FingerPaintPolyline 情報をすばやく取得できます。

FingerPaintCanvasView クラスには、完了したポリラインに対する List オブジェクトも保持されます。

List<FingerPaintPolyline> completedPolylines = new List<FingerPaintPolyline>();

この List に含まれるオブジェクトは、それらが描画された順番になります。

View によって定義された 2 つのメソッドが FingerPaintCanvasView でオーバーライドされます: OnDraw および OnTouchEvent。 その OnDraw オーバーライドでは、ビューは完成したポリラインを描画し、その後、進行中のポリラインを描画します。

OnTouchEvent メソッドのオーバーライドは、ActionIndex プロパティから pointerIndex 値を取得することから始まります。 この ActionIndex 値は複数の指を区別しますが、複数のイベント間で一貫性がありません。 そのため、GetPointerId メソッドからポインター id 値を取得するために pointerIndex を使用します。 この ID は、複数のイベント間で一貫性があります

public override bool OnTouchEvent(MotionEvent args)
{
    // Get the pointer index
    int pointerIndex = args.ActionIndex;

    // Get the id to identify a finger over the course of its progress
    int id = args.GetPointerId(pointerIndex);

    // Use ActionMasked here rather than Action to reduce the number of possibilities
    switch (args.ActionMasked)
    {
        // ...
    }

    // Invalidate to update the view
    Invalidate();

    // Request continued touch input
    return true;
}

オーバーライドでは、Action プロパティではなく switch ステートメント内の ActionMasked プロパティが使用されていることに注意してください。 その理由を説明します。

マルチタッチを処理する場合、Action プロパティには最初の指が画面にタッチすると MotionEventsAction.Down の値が設定され、2 番目と 3 番目の指も画面にタッチすると、それぞれで値 Pointer2DownPointer3Down が設定されます。 4 番目と 5 番目の指が接触すると、Action プロパティには MotionEventsAction 列挙体のメンバーに対応しない数値が設定されます。 値内のビット フラグの値を調べて、その意味を解釈する必要があります。

同様に、指が画面から離れるときは、Action プロパティの値は、2 番目と 3 番目の指で Pointer2UpPointer3Up になり、1 番目の指では Up になります。

ActionMasked プロパティは、複数の指を区別するために ActionIndex プロパティと組み合わせて使用することを目的としているため、値の数がより少なくなります。 指が画面にタッチすると、プロパティは最初の指では MotionEventActions.Down、それ以降の指では PointerDown とのみ等しくなります。 指が画面を離れるときの ActionMasked は、後続の指では値が Pointer1Up になり、最初の指では Up になります。

ActionMasked を使用する場合、ActionIndex は、画面をタッチして離れる後続の指を区別しますが、通常は、MotionEvent オブジェクト内の他のメソッドの引数として以外、その値を使用する必要はありません。 マルチタッチの場合、これらのメソッドの中で最も重要なものの 1 つが上記のコードで呼び出される GetPointerId です。 このメソッドは、特定のイベントを指に関連付けるために辞書のキーに使用できる値を返します。

サンプルの OnTouchEvent オーバーライドは、新しい FingerPaintPolyline オブジェクトを作成しそれを辞書に追加することで、MotionEventActions.DownPointerDown のイベントを同じように処理します。

public override bool OnTouchEvent(MotionEvent args)
{
    // Get the pointer index
    int pointerIndex = args.ActionIndex;

    // Get the id to identify a finger over the course of its progress
    int id = args.GetPointerId(pointerIndex);

    // Use ActionMasked here rather than Action to reduce the number of possibilities
    switch (args.ActionMasked)
    {
        case MotionEventActions.Down:
        case MotionEventActions.PointerDown:

            // Create a Polyline, set the initial point, and store it
            FingerPaintPolyline polyline = new FingerPaintPolyline
            {
                Color = StrokeColor,
                StrokeWidth = StrokeWidth
            };

            polyline.Path.MoveTo(args.GetX(pointerIndex),
                                 args.GetY(pointerIndex));

            inProgressPolylines.Add(id, polyline);
            break;
        // ...
    }
    // ...        
}

pointerIndex は、ビュー内の指の位置を取得するためにも使用されていることに注意してください。 すべてのタッチ情報は、pointerIndex 値に関連付けられます。 id は、複数のメッセージ間で指を一意に識別するため、辞書エントリの作成に使用されます。

同様に、OnTouchEvent オーバーライドでは、OnDraw のオーバーライド中に描画できるように、完成したポリラインを completedPolylines コレクションに転送することで、MotionEventActions.UpPointer1Up も同じように処理されます。 このコードでは、辞書から id エントリも削除されます。

public override bool OnTouchEvent(MotionEvent args)
{
    // ...
    switch (args.ActionMasked)
    {
        // ...
        case MotionEventActions.Up:
        case MotionEventActions.Pointer1Up:

            inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
                                                args.GetY(pointerIndex));

            // Transfer the in-progress polyline to a completed polyline
            completedPolylines.Add(inProgressPolylines[id]);
            inProgressPolylines.Remove(id);
            break;

        case MotionEventActions.Cancel:
            inProgressPolylines.Remove(id);
            break;
    }
    // ...        
}

ここで注意が必要です。

ダウン イベントとアップ イベントの間には、一般的に MotionEventActions.Move イベントが数多くあります。 これらは OnTouchEvent への 1 回の呼び出しにバンドルされ、Down イベントと Up イベントでは異なる方法で処理する必要があります。 ActionIndex プロパティから以前に取得した pointerIndex 値は無視する必要があります。 代わりに、メソッドは 0 と PointerCount プロパティの間をループして複数の pointerIndex 値を取得し、それらの pointerIndex 値ごとに 1 つの id を取得する必要があります。

public override bool OnTouchEvent(MotionEvent args)
{
    // ...
    switch (args.ActionMasked)
    {
        // ...
        case MotionEventActions.Move:

            // Multiple Move events are bundled, so handle them differently
            for (pointerIndex = 0; pointerIndex < args.PointerCount; pointerIndex++)
            {
                id = args.GetPointerId(pointerIndex);

                inProgressPolylines[id].Path.LineTo(args.GetX(pointerIndex),
                                                    args.GetY(pointerIndex));
            }
            break;
        // ...
    }
    // ...        
}

この種類の処理により、サンプルは個々の指を追跡し、画面に結果を描画できます。

FingerPaint の例のスクリーンショット

これで、画面上の個々の指を追跡し、それらを区別する方法がわかりました。