マルチタッチ フィンガー トラッキング
このトピックでは、複数の指のタッチ イベントを追跡する方法について説明します
マルチタッチ アプリケーションでは、画面上で複数の指が同時に動く場合、個々の指を追跡することが必要な場合があります。 一般的なアプリケーションの 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 番目の指も画面にタッチすると、それぞれで値 Pointer2Down
と Pointer3Down
が設定されます。 4 番目と 5 番目の指が接触すると、Action
プロパティには MotionEventsAction
列挙体のメンバーに対応しない数値が設定されます。 値内のビット フラグの値を調べて、その意味を解釈する必要があります。
同様に、指が画面から離れるときは、Action
プロパティの値は、2 番目と 3 番目の指で Pointer2Up
と Pointer3Up
になり、1 番目の指では Up
になります。
ActionMasked
プロパティは、複数の指を区別するために ActionIndex
プロパティと組み合わせて使用することを目的としているため、値の数がより少なくなります。 指が画面にタッチすると、プロパティは最初の指では MotionEventActions.Down
、それ以降の指では PointerDown
とのみ等しくなります。 指が画面を離れるときの ActionMasked
は、後続の指では値が Pointer1Up
になり、最初の指では Up
になります。
ActionMasked
を使用する場合、ActionIndex
は、画面をタッチして離れる後続の指を区別しますが、通常は、MotionEvent
オブジェクト内の他のメソッドの引数として以外、その値を使用する必要はありません。 マルチタッチの場合、これらのメソッドの中で最も重要なものの 1 つが上記のコードで呼び出される GetPointerId
です。 このメソッドは、特定のイベントを指に関連付けるために辞書のキーに使用できる値を返します。
サンプルの OnTouchEvent
オーバーライドは、新しい FingerPaintPolyline
オブジェクトを作成しそれを辞書に追加することで、MotionEventActions.Down
と PointerDown
のイベントを同じように処理します。
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.Up
と Pointer1Up
も同じように処理されます。 このコードでは、辞書から 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;
// ...
}
// ...
}
この種類の処理により、サンプルは個々の指を追跡し、画面に結果を描画できます。
これで、画面上の個々の指を追跡し、それらを区別する方法がわかりました。